Consolidate webapp structure by merging nested components into the main repository
This commit is contained in:
159
orchestrator/src/api/plans.ts
Normal file
159
orchestrator/src/api/plans.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { createHash } from "crypto";
|
||||
import { validatePlan, checkStepDependencies } from "../services/planValidation";
|
||||
import { storePlan, getPlanById, updatePlanSignature } from "../db/plans";
|
||||
import type { Plan, PlanStep } from "../types/plan";
|
||||
|
||||
/**
|
||||
* POST /api/plans
|
||||
* Create a new execution plan
|
||||
*/
|
||||
export async function createPlan(req: Request, res: Response) {
|
||||
try {
|
||||
const plan: Plan = req.body;
|
||||
|
||||
// Validate plan structure
|
||||
const validation = validatePlan(plan);
|
||||
if (!validation.valid) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid plan",
|
||||
errors: validation.errors,
|
||||
});
|
||||
}
|
||||
|
||||
// Check step dependencies
|
||||
const dependencyCheck = checkStepDependencies(plan.steps);
|
||||
if (!dependencyCheck.valid) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid step dependencies",
|
||||
errors: dependencyCheck.errors,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate plan ID and hash
|
||||
const planId = uuidv4();
|
||||
const planHash = createHash("sha256")
|
||||
.update(JSON.stringify(plan))
|
||||
.digest("hex");
|
||||
|
||||
// Store plan
|
||||
const storedPlan = {
|
||||
...plan,
|
||||
plan_id: planId,
|
||||
plan_hash: planHash,
|
||||
created_at: new Date().toISOString(),
|
||||
status: "pending",
|
||||
};
|
||||
|
||||
await storePlan(storedPlan);
|
||||
|
||||
res.status(201).json({
|
||||
plan_id: planId,
|
||||
plan_hash: planHash,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to create plan",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/plans/:planId
|
||||
* Get plan details
|
||||
*/
|
||||
export async function getPlan(req: Request, res: Response) {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const plan = await getPlanById(planId);
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
error: "Plan not found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json(plan);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to get plan",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/plans/:planId/signature
|
||||
* Add user signature to plan
|
||||
*/
|
||||
export async function addSignature(req: Request, res: Response) {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const { signature, messageHash, signerAddress } = req.body;
|
||||
|
||||
if (!signature || !messageHash || !signerAddress) {
|
||||
return res.status(400).json({
|
||||
error: "Missing required fields: signature, messageHash, signerAddress",
|
||||
});
|
||||
}
|
||||
|
||||
const plan = await getPlanById(planId);
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
error: "Plan not found",
|
||||
});
|
||||
}
|
||||
|
||||
// Update plan with signature
|
||||
await updatePlanSignature(planId, {
|
||||
signature,
|
||||
messageHash,
|
||||
signerAddress,
|
||||
signedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
planId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to add signature",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/plans/:planId/validate
|
||||
* Validate plan structure and dependencies
|
||||
*/
|
||||
export async function validatePlanEndpoint(req: Request, res: Response) {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const plan = await getPlanById(planId);
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
error: "Plan not found",
|
||||
});
|
||||
}
|
||||
|
||||
const validation = validatePlan(plan);
|
||||
const dependencyCheck = checkStepDependencies(plan.steps);
|
||||
|
||||
res.json({
|
||||
valid: validation.valid && dependencyCheck.valid,
|
||||
validation: validation,
|
||||
dependencies: dependencyCheck,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to validate plan",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
45
orchestrator/src/api/sse.ts
Normal file
45
orchestrator/src/api/sse.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { executionCoordinator } from "../services/execution";
|
||||
|
||||
/**
|
||||
* GET /api/plans/:planId/status/stream
|
||||
* Server-Sent Events stream for real-time execution status
|
||||
*/
|
||||
export function streamPlanStatus(req: Request, res: Response) {
|
||||
const { planId } = req.params;
|
||||
|
||||
// Set SSE headers
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
|
||||
|
||||
// Send initial connection message
|
||||
res.write(`data: ${JSON.stringify({ type: "connected", planId })}\n\n`);
|
||||
|
||||
// Listen for status updates
|
||||
const statusHandler = (executionId: string, event: any) => {
|
||||
// Only send events for this plan
|
||||
if (event.planId === planId || executionId.includes(planId)) {
|
||||
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
executionCoordinator.onStatus(statusHandler);
|
||||
|
||||
// Handle client disconnect
|
||||
req.on("close", () => {
|
||||
executionCoordinator.off("status", statusHandler);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Keep connection alive with ping
|
||||
const pingInterval = setInterval(() => {
|
||||
res.write(`: ping\n\n`);
|
||||
}, 30000);
|
||||
|
||||
req.on("close", () => {
|
||||
clearInterval(pingInterval);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user