feat: SSE real-time trace streaming + advanced search/filter with URL sync

This commit is contained in:
Vectry
2026-02-10 00:12:32 +00:00
parent 5bb75433aa
commit 47ef3dcbe6
3 changed files with 545 additions and 48 deletions

View File

@@ -254,6 +254,10 @@ export async function GET(request: NextRequest) {
const status = searchParams.get("status");
const search = searchParams.get("search");
const sessionId = searchParams.get("sessionId");
const tags = searchParams.get("tags");
const sort = searchParams.get("sort") ?? "newest";
const dateFrom = searchParams.get("dateFrom");
const dateTo = searchParams.get("dateTo");
// Validate pagination parameters
if (isNaN(page) || page < 1) {
@@ -269,6 +273,20 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}` }, { status: 400 });
}
// Validate sort parameter
const validSorts = ["newest", "oldest", "longest", "shortest", "costliest"];
if (sort && !validSorts.includes(sort)) {
return NextResponse.json({ error: `Invalid sort. Must be one of: ${validSorts.join(", ")}` }, { status: 400 });
}
// Validate date parameters
if (dateFrom && isNaN(Date.parse(dateFrom))) {
return NextResponse.json({ error: "Invalid dateFrom parameter. Must be a valid ISO date string." }, { status: 400 });
}
if (dateTo && isNaN(Date.parse(dateTo))) {
return NextResponse.json({ error: "Invalid dateTo parameter. Must be a valid ISO date string." }, { status: 400 });
}
// Build where clause
const where: Record<string, unknown> = {};
if (status) {
@@ -283,6 +301,50 @@ export async function GET(request: NextRequest) {
if (sessionId) {
where.sessionId = sessionId;
}
if (tags) {
const tagList = tags.split(",").map((t) => t.trim()).filter(Boolean);
if (tagList.length > 0) {
where.tags = {
hasSome: tagList,
};
}
}
if (dateFrom) {
where.createdAt = {
...((where.createdAt as Prisma.TraceWhereInput) ?? {}),
gte: new Date(dateFrom),
};
}
if (dateTo) {
where.createdAt = {
...((where.createdAt as Prisma.TraceWhereInput) ?? {}),
lte: new Date(dateTo),
};
}
// Build order by clause based on sort parameter
let orderBy: Prisma.TraceOrderByWithRelationInput = {
startedAt: "desc",
};
switch (sort) {
case "oldest":
orderBy = { startedAt: "asc" };
break;
case "longest":
orderBy = { totalDuration: "desc" };
break;
case "shortest":
orderBy = { totalDuration: "asc" };
break;
case "costliest":
orderBy = { totalCost: "desc" };
break;
case "newest":
default:
orderBy = { startedAt: "desc" };
break;
}
// Count total traces
const total = await prisma.trace.count({ where });
@@ -303,9 +365,7 @@ export async function GET(request: NextRequest) {
},
},
},
orderBy: {
startedAt: "desc",
},
orderBy,
skip,
take: limit,
});