mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
refactor: extract shared route handlers and modularize plan server (#214)
* refactor: extract shared route handlers and modularize plan server Pull duplicated /api/image, /api/upload, /api/agents handlers and handleServerReady into shared-handlers.ts. Move /api/doc and Obsidian reference endpoints into reference-handlers.ts. Eliminates ~120 lines of verbatim duplication across index.ts, review.ts, and annotate.ts. Closes #210 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: deduplicate OpencodeClient type and fix stale module comment Export OpencodeClient interface from shared-handlers.ts and import it in index.ts and review.ts instead of redefining inline. Also corrects the module doc comment about /api/agents usage scope. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,17 +9,16 @@
|
||||
* PLANNOTATOR_PORT - Fixed port to use (default: random locally, 19432 for remote)
|
||||
*/
|
||||
|
||||
import { mkdirSync } from "fs";
|
||||
import { isRemoteSession, getServerPort } from "./remote";
|
||||
import { openBrowser } from "./browser";
|
||||
import { type DiffType, type GitContext, runGitDiff } from "./git";
|
||||
import { getRepoInfo } from "./repo";
|
||||
import { validateImagePath, validateUploadExtension, UPLOAD_DIR } from "./image";
|
||||
import { handleImage, handleUpload, handleAgents, handleServerReady, type OpencodeClient } from "./shared-handlers";
|
||||
|
||||
// Re-export utilities
|
||||
export { isRemoteSession, getServerPort } from "./remote";
|
||||
export { openBrowser } from "./browser";
|
||||
export { type DiffType, type DiffOption, type GitContext } from "./git";
|
||||
export { handleServerReady as handleReviewServerReady } from "./shared-handlers";
|
||||
|
||||
// --- Types ---
|
||||
|
||||
@@ -45,11 +44,7 @@ export interface ReviewServerOptions {
|
||||
/** Called when server starts with the URL, remote status, and port */
|
||||
onReady?: (url: string, isRemote: boolean, port: number) => void;
|
||||
/** OpenCode client for querying available agents (OpenCode only) */
|
||||
opencodeClient?: {
|
||||
app: {
|
||||
agents: (options?: object) => Promise<{ data?: Array<{ name: string; description?: string; mode: string; hidden?: boolean }> }>;
|
||||
};
|
||||
};
|
||||
opencodeClient?: OpencodeClient;
|
||||
}
|
||||
|
||||
export interface ReviewServerResult {
|
||||
@@ -177,66 +172,17 @@ export async function startReviewServer(
|
||||
|
||||
// API: Serve images (local paths or temp uploads)
|
||||
if (url.pathname === "/api/image") {
|
||||
const imagePath = url.searchParams.get("path");
|
||||
if (!imagePath) {
|
||||
return new Response("Missing path parameter", { status: 400 });
|
||||
}
|
||||
const validation = validateImagePath(imagePath);
|
||||
if (!validation.valid) {
|
||||
return new Response(validation.error!, { status: 403 });
|
||||
}
|
||||
try {
|
||||
const file = Bun.file(validation.resolved);
|
||||
if (!(await file.exists())) {
|
||||
return new Response("File not found", { status: 404 });
|
||||
}
|
||||
return new Response(file);
|
||||
} catch {
|
||||
return new Response("Failed to read file", { status: 500 });
|
||||
}
|
||||
return handleImage(req);
|
||||
}
|
||||
|
||||
// API: Upload image -> save to temp -> return path
|
||||
if (url.pathname === "/api/upload" && req.method === "POST") {
|
||||
try {
|
||||
const formData = await req.formData();
|
||||
const file = formData.get("file") as File;
|
||||
if (!file) {
|
||||
return new Response("No file provided", { status: 400 });
|
||||
}
|
||||
|
||||
const extResult = validateUploadExtension(file.name);
|
||||
if (!extResult.valid) {
|
||||
return Response.json({ error: extResult.error }, { status: 400 });
|
||||
}
|
||||
mkdirSync(UPLOAD_DIR, { recursive: true });
|
||||
const tempPath = `${UPLOAD_DIR}/${crypto.randomUUID()}.${extResult.ext}`;
|
||||
|
||||
await Bun.write(tempPath, file);
|
||||
return Response.json({ path: tempPath, originalName: file.name });
|
||||
} catch (err) {
|
||||
const message =
|
||||
err instanceof Error ? err.message : "Upload failed";
|
||||
return Response.json({ error: message }, { status: 500 });
|
||||
}
|
||||
return handleUpload(req);
|
||||
}
|
||||
|
||||
// API: Get available agents (OpenCode only)
|
||||
if (url.pathname === "/api/agents") {
|
||||
if (!options.opencodeClient) {
|
||||
return Response.json({ agents: [] });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await options.opencodeClient.app.agents({});
|
||||
const agents = (result.data ?? [])
|
||||
.filter((a) => a.mode === "primary" && !a.hidden)
|
||||
.map((a) => ({ id: a.name, name: a.name, description: a.description }));
|
||||
|
||||
return Response.json({ agents });
|
||||
} catch {
|
||||
return Response.json({ agents: [], error: "Failed to fetch agents" });
|
||||
}
|
||||
return handleAgents(options.opencodeClient);
|
||||
}
|
||||
|
||||
// API: Submit review feedback
|
||||
@@ -307,16 +253,3 @@ export async function startReviewServer(
|
||||
stop: () => server.stop(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behavior: open browser for local sessions
|
||||
*/
|
||||
export async function handleReviewServerReady(
|
||||
url: string,
|
||||
isRemote: boolean,
|
||||
_port: number
|
||||
): Promise<void> {
|
||||
if (!isRemote) {
|
||||
await openBrowser(url);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user