// --------------------------------------------------------------------------- // Cloud API — protected core API + public auth endpoints // --------------------------------------------------------------------------- import { HttpApi, HttpApiBuilder, HttpApiSwagger, HttpMiddleware, HttpRouter, HttpServer, } from "@effect/platform"; import { Effect, Layer, Scope } from "effect"; import { setCookie } from "@tanstack/react-start/server"; import { addGroup } from "@executor/api"; import { CoreHandlers, ExecutorService, ExecutionEngineService } from "@executor/api/server"; import { createExecutionEngine } from "@executor/execution"; import { OpenApiGroup, OpenApiExtensionService, OpenApiHandlers } from "@executor/plugin-openapi/api"; import { McpGroup, McpExtensionService, McpHandlers } from "@executor/plugin-mcp/api"; import { GoogleDiscoveryGroup, GoogleDiscoveryExtensionService, GoogleDiscoveryHandlers, } from "@executor/plugin-google-discovery/api"; import { GraphqlGroup, GraphqlExtensionService, GraphqlHandlers } from "@executor/plugin-graphql/api"; import { CloudAuthApi, CloudAuthPublicApi } from "./auth/api"; import { AuthContext, UserStoreService } from "./auth/context"; import { CloudAuthHandlers, CloudAuthPublicHandlers } from "./auth/handlers"; import { WorkOSAuth } from "./auth/workos"; import { DbService } from "./services/db"; import { createTeamExecutor } from "./services/executor"; import { server } from "./env"; const ProtectedCloudApi = addGroup(OpenApiGroup) .add(McpGroup) .add(GoogleDiscoveryGroup) .add(GraphqlGroup) .add(CloudAuthApi); const PublicCloudApi = HttpApi.make("cloudPublic") .add(CloudAuthPublicApi); const ProtectedCloudApiLive = HttpApiBuilder.api(ProtectedCloudApi).pipe( Layer.provide(CoreHandlers), Layer.provide(Layer.mergeAll( OpenApiHandlers, McpHandlers, GoogleDiscoveryHandlers, GraphqlHandlers, CloudAuthHandlers, )), ); const PublicCloudApiLive = HttpApiBuilder.api(PublicCloudApi).pipe( Layer.provide(CloudAuthPublicHandlers), ); const DbLive = DbService.Live; const UserStoreLive = UserStoreService.Live.pipe( Layer.provide(DbLive), ); const SharedServices = Layer.mergeAll( DbLive, UserStoreLive, WorkOSAuth.Default, HttpServer.layerContext, ); const sharedServicesScope = Effect.runSync(Scope.make()); const SharedServicesMemoized = Effect.runSync( Layer.memoize(SharedServices).pipe( Scope.extend(sharedServicesScope), ), ); const publicApiHandler = HttpApiBuilder.toWebHandler( PublicCloudApiLive.pipe( Layer.provideMerge(SharedServicesMemoized), Layer.provideMerge(HttpRouter.setRouterConfig({ maxParamLength: 1000 })), ), { middleware: HttpMiddleware.logger }, ); const parseCookie = (cookieHeader: string | null, name: string): string | null => { if (!cookieHeader) return null; const match = cookieHeader .split(";") .map((value) => value.trim()) .find((value) => value.startsWith(`${name}=`)); if (!match) return null; return match.slice(name.length + 1) || null; }; const isPublicPath = (pathname: string): boolean => pathname === "/auth/login" || pathname === "/auth/callback"; const unauthorized = (message: string): Response => Response.json({ error: message }, { status: 401 }); const COOKIE_OPTIONS = { path: "/", httpOnly: true, sameSite: "lax" as const, maxAge: 60 * 60 * 24 * 7, secure: server.NODE_ENV === "production", }; const resolveAuth = (request: Request) => Effect.gen(function* () { const workos = yield* WorkOSAuth; return yield* workos.authenticateRequest(request); }); const resolveTeamId = ( auth: { readonly userId: string; readonly email: string; readonly firstName: string | null | undefined; readonly lastName: string | null | undefined; readonly avatarUrl: string | null | undefined; }, cookieTeamId: string | null, ) => Effect.gen(function* () { if (cookieTeamId) return cookieTeamId; const users = yield* UserStoreService; const teams = yield* users.use((store) => store.getTeamsForUser(auth.userId)); if (teams.length > 0) return teams[0]!.teamId; const user = yield* users.use((store) => store.upsertUser({ id: auth.userId, email: auth.email, name: `${auth.firstName ?? ""} ${auth.lastName ?? ""}`.trim() || undefined, avatarUrl: auth.avatarUrl ?? undefined, }), ); const team = yield* users.use((store) => store.createTeam(`${user.name ?? user.email}'s Team`), ); yield* users.use((store) => store.addMember(team.id, user.id, "owner")); return team.id; }); const resolveExecutor = (teamId: string) => Effect.gen(function* () { const users = yield* UserStoreService; const team = yield* users.use((store) => store.getTeam(teamId)); const teamName = team?.name ?? "Unknown Team"; const encryptionKey = server.ENCRYPTION_KEY; return yield* createTeamExecutor(teamId, teamName, encryptionKey); }); type ResolvedAuth = Exclude>, null>; type TeamExecutor = Effect.Effect.Success>; const createProtectedHandler = ( auth: ResolvedAuth, teamId: string, executor: TeamExecutor, ) => { const engine = createExecutionEngine({ executor }); const requestServices = Layer.mergeAll( Layer.succeed(AuthContext, { userId: auth.userId, teamId, email: auth.email, name: `${auth.firstName ?? ""} ${auth.lastName ?? ""}`.trim() || null, avatarUrl: auth.avatarUrl ?? null, }), Layer.succeed(ExecutorService, executor), Layer.succeed(ExecutionEngineService, engine), Layer.succeed(OpenApiExtensionService, executor.openapi), Layer.succeed(McpExtensionService, executor.mcp), Layer.succeed(GoogleDiscoveryExtensionService, executor.googleDiscovery), Layer.succeed(GraphqlExtensionService, executor.graphql), ); return HttpApiBuilder.toWebHandler( HttpApiSwagger.layer({ path: "/docs" }).pipe( Layer.provideMerge(HttpApiBuilder.middlewareOpenApi()), Layer.provideMerge(ProtectedCloudApiLive), Layer.provideMerge(requestServices), Layer.provideMerge(SharedServicesMemoized), Layer.provideMerge(HttpRouter.setRouterConfig({ maxParamLength: 1000 })), ), { middleware: HttpMiddleware.logger }, ); }; const closeExecutor = (executor: TeamExecutor) => executor.close().pipe( Effect.orElseSucceed(() => undefined), ); type ProtectedHandler = ReturnType; const disposeProtectedHandler = (handler: ProtectedHandler) => Effect.promise(() => handler.dispose()).pipe( Effect.orElseSucceed(() => undefined), ); export const handleApiRequest = async (request: Request): Promise => { const pathname = new URL(request.url).pathname; if (isPublicPath(pathname)) { return publicApiHandler.handler(request); } const requestProgram = Effect.gen(function* () { const auth = yield* resolveAuth(request); if (!auth) return unauthorized("Unauthorized"); const cookieTeamId = parseCookie(request.headers.get("cookie"), "executor_team"); const teamId = yield* resolveTeamId(auth, cookieTeamId); const executor = yield* Effect.acquireRelease( resolveExecutor(teamId), closeExecutor, ); const handler = yield* Effect.acquireRelease( Effect.sync(() => createProtectedHandler(auth, teamId, executor)), disposeProtectedHandler, ); const response = yield* Effect.promise(() => handler.handler(request)); const refreshedSession = auth.refreshedSession; if (refreshedSession) { yield* Effect.sync(() => { setCookie("wos-session", refreshedSession, COOKIE_OPTIONS); }); } if (!cookieTeamId) { yield* Effect.sync(() => { setCookie("executor_team", teamId, COOKIE_OPTIONS); }); } return response; }); return Effect.runPromise( requestProgram.pipe( Effect.provide(SharedServicesMemoized), Effect.scoped, ), ); };