Files
saltbo__agent-kanban/server/http/middleware/v2Contract.ts

66 lines
3.5 KiB
TypeScript

import type { Env } from "@server/env";
import { V2_API_VERSION as SHARED_V2_API_VERSION } from "@shared";
import type { Context, Next } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
export const V2_API_VERSION = SHARED_V2_API_VERSION;
export function isPublishedV2Operation(method: string, path: string): boolean {
if (/^\/api\/tasks\/[^/]+\/events$/.test(path)) return method === "GET";
if (/^\/api\/tasks\/[^/]+\/claims$/.test(path)) return method === "POST";
if (path === "/api/boards") return method === "GET" || method === "POST";
if (/^\/api\/boards\/[^/]+$/.test(path)) return method === "GET" || method === "PATCH" || method === "DELETE";
if (/^\/api\/boards\/[^/]+\/labels$/.test(path)) return method === "POST";
if (/^\/api\/boards\/[^/]+\/labels\/[^/]+$/.test(path)) return method === "PATCH" || method === "DELETE";
if (/^\/api\/boards\/[^/]+\/stream$/.test(path)) return method === "GET";
if (path === "/api/repositories") return method === "GET" || method === "POST";
if (/^\/api\/repositories\/[^/]+$/.test(path)) return method === "GET" || method === "DELETE";
if (path === "/api/github-app/config" || path === "/api/github-app/repositories") return method === "GET";
if (/^\/api\/repository-installations\/[^/]+$/.test(path)) return method === "PUT";
if (/^\/api\/agents\/[^/]+\/permissions$/.test(path)) return method === "POST";
if (path === "/api/agents") return method === "GET" || method === "POST";
if (/^\/api\/agents\/[^/]+$/.test(path)) return method === "GET";
if (path === "/api/machines") return method === "GET" || method === "POST";
if (/^\/api\/machines\/[^/]+$/.test(path)) return method === "GET" || method === "DELETE";
if (path === "/api/tasks") return method === "GET" || method === "POST";
if (/^\/api\/tasks\/[^/]+$/.test(path)) return method === "GET" || method === "PATCH" || method === "DELETE";
if (/^\/api\/tasks\/[^/]+\/session(?:\/ws)?$/.test(path)) return method === "GET";
if (/^\/api\/tasks\/[^/]+\/stream$/.test(path)) return method === "GET";
if (/^\/api\/tasks\/[^/]+\/notes$/.test(path)) return method === "GET" || method === "POST";
if (/^\/api\/tasks\/[^/]+\/notes\/[^/]+$/.test(path)) return method === "GET";
return false;
}
export async function v2ApiVersionMiddleware(c: Context<{ Bindings: Env }>, next: Next): Promise<Response | undefined> {
const requestedVersion = c.req.header("API-Version");
if (requestedVersion !== undefined && requestedVersion !== V2_API_VERSION) {
return v2Problem(c, 400, "invalid-api-version", "Invalid API version", `Unsupported API-Version: ${requestedVersion}`);
}
c.header("API-Version", V2_API_VERSION);
c.header("Vary", "API-Version");
await next();
if (c.res.headers.has("ETag")) {
// If-Match requires the strong validator issued here, unchanged by edge compression.
c.header("Cache-Control", "private, no-cache, no-transform");
}
return undefined;
}
export function effectiveApiVersion(c: Context): string {
return c.req.header("API-Version") ?? V2_API_VERSION;
}
export function v2Problem(c: Context<{ Bindings: Env }>, status: ContentfulStatusCode, type: string, title: string, detail: string): Response {
return c.json(
{
type: new URL(`/api/problems/${type}`, c.req.url).toString(),
title,
status,
detail,
instance: `urn:request:${c.get("requestId")}`,
},
status,
{ "Content-Type": "application/problem+json", "API-Version": V2_API_VERSION, Vary: "API-Version" },
);
}