Rewrite Next.js local BFF routes and mock catalog.
Projects, report, and graph JSON handlers overlay from periscope/src. Clerk proxy stays the inherited open-core seam.
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
const SIBLING_ROOT = path.resolve(process.cwd(), "..");
|
||||||
|
|
||||||
|
function safeSegment(id: string): string | null {
|
||||||
|
if (!id || id.includes("/") || id.includes("\\") || id === "." || id === "..") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function jsonFromProjectFile(
|
||||||
|
id: string,
|
||||||
|
filename: string,
|
||||||
|
missing: string,
|
||||||
|
): Promise<NextResponse> {
|
||||||
|
const projectId = safeSegment(id);
|
||||||
|
if (!projectId) {
|
||||||
|
return NextResponse.json({ error: missing }, { status: 404 });
|
||||||
|
}
|
||||||
|
const filePath = path.join(SIBLING_ROOT, projectId, filename);
|
||||||
|
try {
|
||||||
|
const raw = await readFile(filePath, "utf-8");
|
||||||
|
return NextResponse.json(JSON.parse(raw) as unknown);
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ error: missing }, { status: 404 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { jsonFromProjectFile } from "../../_project-json";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
_req: Request,
|
||||||
|
{ params }: { params: Promise<{ id: string }> },
|
||||||
|
) {
|
||||||
|
const { id } = await params;
|
||||||
|
return jsonFromProjectFile(id, "design_graph.json", "Graph not found");
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { PROJECTS } from "@/lib/mock-data";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return NextResponse.json(PROJECTS);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { jsonFromProjectFile } from "../../_project-json";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
_req: Request,
|
||||||
|
{ params }: { params: Promise<{ id: string }> },
|
||||||
|
) {
|
||||||
|
const { id } = await params;
|
||||||
|
return jsonFromProjectFile(id, "report.json", "Report not found");
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import type { PipelineStep, Project } from "./types";
|
||||||
|
|
||||||
|
/** Demo row for the local Next BFF when the FastAPI catalog is not in front. */
|
||||||
|
export const PROJECTS: Project[] = [
|
||||||
|
{
|
||||||
|
id: "simple_project",
|
||||||
|
name: "TI MSP Tutorial Board",
|
||||||
|
created: "2026-04-09T00:00:00Z",
|
||||||
|
status: "complete",
|
||||||
|
summary: { total: 8, ERROR: 2, WARNING: 4, INFO: 2 },
|
||||||
|
hasNetlist: true,
|
||||||
|
hasBom: true,
|
||||||
|
datasheetCount: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function pending(title: string, description: string, substeps: PipelineStep["substeps"]): PipelineStep {
|
||||||
|
return { title, description, status: "pending", substeps };
|
||||||
|
}
|
||||||
|
|
||||||
|
function pendingItem(key: string, label: string) {
|
||||||
|
return { key, label, status: "pending" as const };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPipelineSteps(): PipelineStep[] {
|
||||||
|
return [
|
||||||
|
pending("IC Datasheet Extraction", "Parse component datasheets and extract constraints", [
|
||||||
|
pendingItem("SPX3819M5-L-3-3/TR", "SPX3819M5-L-3-3/TR (U1)"),
|
||||||
|
pendingItem("CH340E", "CH340E (U2)"),
|
||||||
|
pendingItem("MSPM0G3507SPTR", "MSPM0G3507SPTR (U3)"),
|
||||||
|
]),
|
||||||
|
pending("Passive Pattern Extraction", "Resolve passive component values from MPN patterns", [
|
||||||
|
pendingItem("samsung", "Samsung capacitors"),
|
||||||
|
pendingItem("uniroyal", "Uniroyal resistors"),
|
||||||
|
]),
|
||||||
|
pending("Build Design Graph", "Parse BOM and netlist into structured graph", [
|
||||||
|
pendingItem("parse-bom", "Parse BOM (17 components)"),
|
||||||
|
pendingItem("parse-netlist", "Parse netlist (36 nets)"),
|
||||||
|
pendingItem("enrich", "Enrich with datasheet data"),
|
||||||
|
]),
|
||||||
|
pending("Review Design", "Review each IC against its datasheet", [
|
||||||
|
pendingItem("U1", "Review U1 — LDO"),
|
||||||
|
pendingItem("U2", "Review U2 — USB Bridge"),
|
||||||
|
pendingItem("U3", "Review U3 — MCU"),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""Next.js BFF routes for local JSON live under periscope/src."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SRC = ROOT / "periscope" / "src" / "frontend" / "src"
|
||||||
|
|
||||||
|
|
||||||
|
def _text(rel: str) -> str:
|
||||||
|
path = SRC / rel
|
||||||
|
assert path.is_file(), rel
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_bff_routes_are_src():
|
||||||
|
for rel in (
|
||||||
|
"lib/mock-data.ts",
|
||||||
|
"app/(app)/api/_project-json.ts",
|
||||||
|
"app/(app)/api/projects/route.ts",
|
||||||
|
"app/(app)/api/report/[id]/route.ts",
|
||||||
|
"app/(app)/api/graph/[id]/route.ts",
|
||||||
|
):
|
||||||
|
head = _text(rel)[:400]
|
||||||
|
assert "Native Periscope overlay" not in head
|
||||||
|
|
||||||
|
|
||||||
|
def test_projects_route_serves_mock_catalog():
|
||||||
|
route = _text("app/(app)/api/projects/route.ts")
|
||||||
|
assert "export async function GET" in route
|
||||||
|
assert "PROJECTS" in route
|
||||||
|
mock = _text("lib/mock-data.ts")
|
||||||
|
assert "export const PROJECTS" in mock
|
||||||
|
assert "simple_project" in mock
|
||||||
|
assert "export function createPipelineSteps" in mock
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_and_graph_read_sibling_json():
|
||||||
|
report = _text("app/(app)/api/report/[id]/route.ts")
|
||||||
|
graph = _text("app/(app)/api/graph/[id]/route.ts")
|
||||||
|
helper = _text("app/(app)/api/_project-json.ts")
|
||||||
|
assert "report.json" in report
|
||||||
|
assert "Report not found" in report
|
||||||
|
assert "design_graph.json" in graph
|
||||||
|
assert "Graph not found" in graph
|
||||||
|
assert "jsonFromProjectFile" in helper
|
||||||
|
assert "params: Promise<{ id: string }>" in report
|
||||||
|
assert "params: Promise<{ id: string }>" in graph
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_stays_clerk_seam_in_dependency():
|
||||||
|
src_proxy = ROOT / "periscope" / "src" / "frontend" / "src" / "proxy.ts"
|
||||||
|
dep_proxy = ROOT / "periscope" / "dependency" / "frontend" / "src" / "proxy.ts"
|
||||||
|
assert not src_proxy.exists()
|
||||||
|
text = dep_proxy.read_text(encoding="utf-8")
|
||||||
|
assert "Open-core seam" in text
|
||||||
|
assert "export function proxy" in text
|
||||||
Reference in New Issue
Block a user