(req: Request, origin: string)
| 276 | } |
| 277 | |
| 278 | async function handlePublish(req: Request, origin: string): Promise<Response> { |
| 279 | let body: any; |
| 280 | try { |
| 281 | body = await req.json(); |
| 282 | } catch { |
| 283 | return Response.json({ error: "Invalid JSON" }, { status: 400 }); |
| 284 | } |
| 285 | if (!body || typeof body !== "object") { |
| 286 | return Response.json({ error: "Expected JSON object" }, { status: 400 }); |
| 287 | } |
| 288 | const htmlPath = typeof body.html === "string" ? body.html : ""; |
| 289 | if (!htmlPath) return Response.json({ error: "Missing 'html' field" }, { status: 400 }); |
| 290 | if (!fs.existsSync(htmlPath)) { |
| 291 | return Response.json({ error: `HTML file not found: ${htmlPath}` }, { status: 400 }); |
| 292 | } |
| 293 | let resolvedHtml: string; |
| 294 | let sourceDir: string; |
| 295 | try { |
| 296 | resolvedHtml = fs.realpathSync(path.resolve(htmlPath)); |
| 297 | sourceDir = fs.realpathSync(path.dirname(resolvedHtml)); |
| 298 | } catch (e: any) { |
| 299 | return Response.json({ error: `Cannot resolve path: ${e.message}` }, { status: 400 }); |
| 300 | } |
| 301 | if (!fs.statSync(resolvedHtml).isFile()) { |
| 302 | return Response.json( |
| 303 | { error: `'html' must be a file, not a directory: ${htmlPath}` }, |
| 304 | { status: 400 }, |
| 305 | ); |
| 306 | } |
| 307 | |
| 308 | // sourceDir comes from realpath(html), not from the body — Codex finding: |
| 309 | // body-supplied sourceDir is a local trust boundary the daemon shouldn't cross. |
| 310 | const existing = findActiveBoardForSourceDir(sourceDir); |
| 311 | if (existing) { |
| 312 | return Response.json( |
| 313 | { |
| 314 | error: "Source directory already in use by an active board", |
| 315 | existing: { |
| 316 | id: existing.id, |
| 317 | url: `${origin}/boards/${existing.id}/`, |
| 318 | state: existing.state, |
| 319 | }, |
| 320 | }, |
| 321 | { status: 409 }, |
| 322 | ); |
| 323 | } |
| 324 | if (nonDoneCount() >= MAX_BOARDS) { |
| 325 | return Response.json( |
| 326 | { |
| 327 | error: `Cannot publish: ${MAX_BOARDS} non-done boards already exist. Submit or close some first.`, |
| 328 | }, |
| 329 | { status: 503 }, |
| 330 | ); |
| 331 | } |
| 332 | |
| 333 | const id = newBoardId(); |
| 334 | const htmlContent = fs.readFileSync(resolvedHtml, "utf-8"); |
| 335 | const now = Date.now(); |
no test coverage detected