Files
mesh-forge/convex/http.ts
2026-04-10 06:20:29 -07:00

127 lines
4.0 KiB
TypeScript

import { httpRouter } from "convex/server"
import type { Id } from "./_generated/dataModel"
import { internal } from "./_generated/api"
import { httpAction } from "./_generated/server"
import { auth } from "./auth"
const http = httpRouter()
auth.addHttpRoutes(http)
function verifyBearer(request: Request): boolean {
const buildToken = process.env.CONVEX_BUILD_TOKEN
if (!buildToken) return false
const authHeader = request.headers.get("Authorization")
if (!authHeader?.startsWith("Bearer ")) return false
return authHeader.slice(7) === buildToken
}
function mapWebhookStatus(state: string): "running" | "succeeded" | "failed" {
const s = state.toLowerCase()
if (s === "running" || s === "queued") return "running"
if (s === "succeeded" || s === "success" || s === "completed") return "succeeded"
if (s === "failed" || s === "failure") return "failed"
return "running"
}
http.route({
path: "/ingest-repo-build",
method: "POST",
handler: httpAction(async (ctx, request) => {
if (!verifyBearer(request)) {
return new Response("Unauthorized", { status: 401 })
}
const payload = (await request.json()) as {
repo_build_id?: string
state?: string
github_run_id?: string | number
r2ObjectKey?: string
errorSummary?: string
}
if (!payload.repo_build_id || !payload.state) {
return new Response("Missing repo_build_id or state", { status: 400 })
}
const runId = payload.github_run_id !== undefined ? Number(payload.github_run_id) : undefined
await ctx.runMutation(internal.repoBuilds.patchFromWebhook, {
buildId: payload.repo_build_id as Id<"repoBuilds">,
status: mapWebhookStatus(payload.state),
githubRunId: Number.isFinite(runId) ? runId : undefined,
r2ObjectKey: payload.r2ObjectKey,
errorSummary: payload.errorSummary,
})
return new Response(null, { status: 200 })
}),
})
http.route({
path: "/ingest-repo-build-progress",
method: "POST",
handler: httpAction(async (ctx, request) => {
if (!verifyBearer(request)) {
return new Response("Unauthorized", { status: 401 })
}
const payload = (await request.json()) as {
repo_build_id?: string
step_index?: number
step_total?: number
label?: string
}
if (
!payload.repo_build_id ||
typeof payload.step_index !== "number" ||
typeof payload.step_total !== "number" ||
typeof payload.label !== "string"
) {
return new Response("Missing repo_build_id, step_index, step_total, or label", { status: 400 })
}
await ctx.runMutation(internal.repoBuilds.patchCiProgress, {
buildId: payload.repo_build_id as Id<"repoBuilds">,
stepIndex: payload.step_index,
stepTotal: payload.step_total,
label: payload.label,
})
return new Response(null, { status: 200 })
}),
})
/** Legacy: old workflows posting build_id + state */
http.route({
path: "/github-webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
if (!verifyBearer(request)) {
return new Response("Unauthorized", { status: 401 })
}
const payload = (await request.json()) as {
build_id?: string
repo_build_id?: string
state?: string
github_run_id?: string | number
firmwarePath?: string
r2ObjectKey?: string
errorSummary?: string
}
const id = payload.repo_build_id || payload.build_id
if (!id || !payload.state) {
return new Response("Missing build id or state", { status: 400 })
}
const runId = payload.github_run_id !== undefined ? Number(payload.github_run_id) : undefined
await ctx.runMutation(internal.repoBuilds.patchFromWebhook, {
buildId: id as Id<"repoBuilds">,
status: mapWebhookStatus(payload.state),
githubRunId: Number.isFinite(runId) ? runId : undefined,
r2ObjectKey: payload.r2ObjectKey || payload.firmwarePath,
errorSummary: payload.errorSummary,
})
return new Response(null, { status: 200 })
}),
})
export default http