Files
ProjectE/apps/api/src/index.ts
T
bot-hermes c6328c120a feat: implement PL-7, PL-8, PL-9 — state-driven board, module/cycle views, link panels + graph edges
PL-7 — Task Board Columns from States:
- Add State, Module, Cycle, Link TypeScript types to types/index.ts
- Add stateId, moduleId, cycleId, trackedMinutes to Task type
- Rewrite tasks.tsx: fetch states from API, render 5 state-group columns
  (backlog/unstarted/started/completed/cancelled), drag-and-drop updates
  stateId via PATCH /tasks/:id, colored state badges, state filter dropdown,
  project filter
- Fix deprecated POST /tasks/:id/status → PATCH /tasks/:id with stateId
- Update tasks/.tsx: state selector dropdown replaces hardcoded status enum,
  toggle complete uses state-based approach, dependencies replaced with
  link-based UI using /api/links

PL-8 — Module + Cycle Views:
- Create apps/api/src/routes/cycles.ts: full CRUD + task assignment/removal
- Create apps/api/src/routes/links.ts: list/create/delete links between entities
- Register cycleRoutes and linkRoutes in API index
- Add Modules tab to project detail: list modules, expand to show tasks,
  add/remove tasks from modules, create/edit/delete module dialogs
- Add Cycles tab to project detail: sprint board grid, backlog lane,
  manual task transfer between cycles and backlog, create/edit cycle dialogs
- Fix ProjectTasks toggle to use PATCH with stateId instead of deprecated endpoint

PL-9 — Link Panels + Graph:
- Update graph API to read links bidirectionally (source OR target)
- Add link type color map (LINK_TYPE_COLORS) for edge rendering
- Graph edges now colored by linkType (blocks=red, relates=gray, etc.)
- Filter panel shows link types with color indicators
- Task detail Dependencies tab now uses /api/links for add/remove links
- Added link type selector (blocks/relates/parent-child/created-from)
2026-09-07 20:24:54 +00:00

106 lines
4.0 KiB
TypeScript

import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { db, errorLogs } from "@project-e/db";
import { authMiddleware } from "./middleware/auth";
import { authRoutes } from "./routes/auth";
import { mcpRoutes } from "./routes/mcp";
import { realtimeRoutes } from "./routes/realtime";
import { domainRoutes } from "./routes/domains";
import { taskRoutes } from "./routes/tasks";
import { habitRoutes } from "./routes/habits";
import { projectRoutes } from "./routes/projects";
import { noteRoutes } from "./routes/notes";
import { searchRoutes } from "./routes/search";
import { calendarRoutes } from "./routes/calendar";
import { graphRoutes } from "./routes/graph";
import { dashboardRoutes } from "./routes/dashboard";
import { agentRoutes } from "./routes/agents";
import { webhookRoutes } from "./routes/webhooks";
import { commentRoutes } from "./routes/comments";
import { dailyNoteRoutes } from "./routes/daily-notes";
import { tagRoutes } from "./routes/tags";
import { customFieldRoutes } from "./routes/custom-fields";
import { errorLogRoutes } from "./routes/error-log";
import { analyticsRoutes } from "./routes/analytics";
import { activityRoutes } from "./routes/activity";
import { importExportRoutes } from "./routes/import-export";
import { notificationRoutes } from "./routes/notifications";
import { stateRoutes } from "./routes/states";
import { moduleRoutes } from "./routes/modules";
import { cycleRoutes } from "./routes/cycles";
import { linkRoutes } from "./routes/links";
import { healthHandler } from "./routes/health";
const app = new Hono();
// Middleware
app.use("*", cors({ origin: "http://localhost:3000", credentials: true }));
app.use("*", logger());
app.use("*", authMiddleware);
// Health check — expanded with DB ping
app.get("/api/health", async (c) => {
const result = await healthHandler();
return c.json(result);
});
// Routes
app.route("/api/auth", authRoutes);
app.route("/api/domains", domainRoutes);
app.route("/api/projects/:projectId/modules", moduleRoutes);
app.route("/api/projects/:projectId/cycles", cycleRoutes);
app.route("/api/modules", moduleRoutes);
app.route("/api/tasks", taskRoutes);
app.route("/api/habits", habitRoutes);
app.route("/api/projects", projectRoutes);
app.route("/api/notes", noteRoutes);
app.route("/api/search", searchRoutes);
app.route("/api/calendar", calendarRoutes);
app.route("/api/graph", graphRoutes);
app.route("/api/dashboard", dashboardRoutes);
app.route("/api/agents", agentRoutes);
app.route("/api/webhooks", webhookRoutes);
app.route("/api/comments", commentRoutes);
app.route("/api/daily-notes", dailyNoteRoutes);
app.route("/api/tags", tagRoutes);
app.route("/api/custom-fields", customFieldRoutes);
app.route("/api/error-log", errorLogRoutes);
app.route("/api/analytics", analyticsRoutes);
app.route("/api/activity", activityRoutes);
app.route("/api/notifications", notificationRoutes);
app.route("/api/states", stateRoutes);
app.route("/api/cycles", cycleRoutes);
app.route("/api/links", linkRoutes);
app.route("/api", importExportRoutes);
app.route("/api", realtimeRoutes);
app.route("/api/mcp", mcpRoutes);
// Persist uncaught server errors so the Settings → Error Log tab shows real
// diagnostics instead of always being empty. Errors already caught by route
// handlers (which return 500 JSON themselves) still log to the console.
app.onError((err, c) => {
console.error("[api] uncaught error:", err);
try {
void db.insert(errorLogs).values({
level: "error",
source: c.req.path,
message: err instanceof Error ? err.message : String(err),
stackTrace: err instanceof Error ? err.stack ?? null : null,
metadata: { method: c.req.method },
});
} catch {
// Logging must never break the error response.
}
return c.json({ error: { code: "INTERNAL_ERROR", message: "Internal server error" } }, 500);
});
const port = parseInt(process.env.PORT || "3001", 10);
export default {
port,
fetch: app.fetch,
};
console.log("API server listening on :" + port);