import { ClientOrchestrationCommand, OrchestrationDispatchCommandError, OrchestrationGetSnapshotError, type OrchestrationReadModel, } from "@t3tools/contracts"; import { Effect } from "effect"; import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { ServerAuth } from "../auth/Services/ServerAuth.ts"; import { normalizeDispatchCommand } from "./Normalizer.ts"; import { OrchestrationEngineService } from "./Services/OrchestrationEngine.ts"; import { ProjectionSnapshotQuery } from "./Services/ProjectionSnapshotQuery.ts"; const respondToOrchestrationHttpError = ( error: OrchestrationDispatchCommandError | OrchestrationGetSnapshotError, ) => Effect.gen(function* () { if (error._tag === "OrchestrationGetSnapshotError") { yield* Effect.logError("orchestration http route failed", { message: error.message, cause: error.cause, }); return HttpServerResponse.jsonUnsafe({ error: error.message }, { status: 500 }); } return HttpServerResponse.jsonUnsafe({ error: error.message }, { status: 400 }); }); const authenticateOwnerSession = Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const serverAuth = yield* ServerAuth; const session = yield* serverAuth.authenticateHttpRequest(request); if (session.role !== "owner") { return yield* new OrchestrationDispatchCommandError({ message: "Only owner sessions can manage projects.", }); } return session; }); export const orchestrationSnapshotRouteLayer = HttpRouter.add( "GET", "/api/orchestration/snapshot", Effect.gen(function* () { yield* authenticateOwnerSession; const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; const snapshot = yield* projectionSnapshotQuery.getSnapshot().pipe( Effect.mapError( (cause) => new OrchestrationGetSnapshotError({ message: "Failed to load orchestration snapshot.", cause, }), ), ); return HttpServerResponse.jsonUnsafe(snapshot satisfies OrchestrationReadModel, { status: 200, }); }).pipe( Effect.catchTag("OrchestrationDispatchCommandError", respondToOrchestrationHttpError), Effect.catchTag("OrchestrationGetSnapshotError", respondToOrchestrationHttpError), ), ); export const orchestrationDispatchRouteLayer = HttpRouter.add( "POST", "/api/orchestration/dispatch", Effect.gen(function* () { yield* authenticateOwnerSession; const orchestrationEngine = yield* OrchestrationEngineService; const command = yield* HttpServerRequest.schemaBodyJson(ClientOrchestrationCommand).pipe( Effect.mapError( (cause) => new OrchestrationDispatchCommandError({ message: "Invalid orchestration command payload.", cause, }), ), ); const normalizedCommand = yield* normalizeDispatchCommand(command); const result = yield* orchestrationEngine.dispatch(normalizedCommand).pipe( Effect.mapError( (cause) => new OrchestrationDispatchCommandError({ message: "Failed to dispatch orchestration command.", cause, }), ), ); return HttpServerResponse.jsonUnsafe(result, { status: 200 }); }).pipe(Effect.catchTag("OrchestrationDispatchCommandError", respondToOrchestrationHttpError)), );