76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import { CLOUD_API_SERVICE_ID } from "../audit/index.mjs";
|
|
import { getHeader, parsePositiveInteger, readBody, sendJson } from "./server-http-utils.ts";
|
|
import {
|
|
getSkillTree,
|
|
listSkills,
|
|
readSkillTextFile,
|
|
uploadUserSkill
|
|
} from "./skills-store.ts";
|
|
|
|
const DEFAULT_SKILL_UPLOAD_BODY_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
|
|
export async function handleSkillsHttp(request, response, url, options = {}) {
|
|
try {
|
|
if (request.method === "GET" && url.pathname === "/v1/skills") {
|
|
sendJson(response, 200, await listSkills({ env: options.env ?? process.env }));
|
|
return;
|
|
}
|
|
|
|
if (request.method === "POST" && url.pathname === "/v1/skills/uploads") {
|
|
const payload = await readJsonBody(request, skillUploadBodyLimit(options.env));
|
|
sendJson(response, 201, await uploadUserSkill(payload, { env: options.env ?? process.env }));
|
|
return;
|
|
}
|
|
|
|
const treeMatch = url.pathname.match(/^\/v1\/skills\/([^/]+)\/tree$/u);
|
|
if (request.method === "GET" && treeMatch) {
|
|
sendJson(response, 200, await getSkillTree(decodeURIComponent(treeMatch[1]), { env: options.env ?? process.env }));
|
|
return;
|
|
}
|
|
|
|
const fileMatch = url.pathname.match(/^\/v1\/skills\/([^/]+)\/files$/u);
|
|
if (request.method === "GET" && fileMatch) {
|
|
sendJson(
|
|
response,
|
|
200,
|
|
await readSkillTextFile(decodeURIComponent(fileMatch[1]), url.searchParams.get("path") || "SKILL.md", { env: options.env ?? process.env })
|
|
);
|
|
return;
|
|
}
|
|
|
|
sendJson(response, 404, skillsErrorPayload(404, "skill_route_not_found", "Skill route is not implemented.", request));
|
|
} catch (error) {
|
|
const statusCode = Number.isInteger(error?.statusCode) ? error.statusCode : 500;
|
|
sendJson(response, statusCode, skillsErrorPayload(statusCode, error?.code || "skill_request_failed", error?.message || "Skill request failed.", request));
|
|
}
|
|
}
|
|
|
|
async function readJsonBody(request, limitBytes) {
|
|
const body = await readBody(request, limitBytes);
|
|
try {
|
|
return body ? JSON.parse(body) : {};
|
|
} catch (error) {
|
|
const wrapped = new Error(`Invalid JSON body: ${error.message}`);
|
|
Object.assign(wrapped, { statusCode: 400, code: "parse_error" });
|
|
throw wrapped;
|
|
}
|
|
}
|
|
|
|
function skillUploadBodyLimit(env = process.env) {
|
|
return parsePositiveInteger(env?.HWLAB_SKILL_UPLOAD_BODY_LIMIT_BYTES, DEFAULT_SKILL_UPLOAD_BODY_LIMIT_BYTES);
|
|
}
|
|
|
|
function skillsErrorPayload(statusCode, code, message, request) {
|
|
return {
|
|
serviceId: CLOUD_API_SERVICE_ID,
|
|
route: request?.url || "/v1/skills",
|
|
status: "failed",
|
|
error: {
|
|
code,
|
|
message,
|
|
httpStatus: statusCode,
|
|
traceId: getHeader(request, "x-trace-id") || null
|
|
}
|
|
};
|
|
}
|