a4ba4a72cd
Introduce MarkdownEditor built on @mdxeditor/editor with headings, lists, links, quotes, image upload with drag-and-drop, and themed CSS integration. Add asset image upload API (routes, service, storage) and wire image upload into InlineEditor multiline mode, NewIssueDialog, NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail description fields. Tighten prompt template editor styling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import express, { Router } from "express";
|
|
import path from "node:path";
|
|
import fs from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import type { Db } from "@paperclip/db";
|
|
import type { StorageService } from "./storage/types.js";
|
|
import { httpLogger, errorHandler } from "./middleware/index.js";
|
|
import { actorMiddleware } from "./middleware/auth.js";
|
|
import { healthRoutes } from "./routes/health.js";
|
|
import { companyRoutes } from "./routes/companies.js";
|
|
import { agentRoutes } from "./routes/agents.js";
|
|
import { projectRoutes } from "./routes/projects.js";
|
|
import { issueRoutes } from "./routes/issues.js";
|
|
import { goalRoutes } from "./routes/goals.js";
|
|
import { approvalRoutes } from "./routes/approvals.js";
|
|
import { secretRoutes } from "./routes/secrets.js";
|
|
import { costRoutes } from "./routes/costs.js";
|
|
import { activityRoutes } from "./routes/activity.js";
|
|
import { dashboardRoutes } from "./routes/dashboard.js";
|
|
import { sidebarBadgeRoutes } from "./routes/sidebar-badges.js";
|
|
import { llmRoutes } from "./routes/llms.js";
|
|
import { assetRoutes } from "./routes/assets.js";
|
|
|
|
type UiMode = "none" | "static" | "vite-dev";
|
|
|
|
export async function createApp(db: Db, opts: { uiMode: UiMode; storageService: StorageService }) {
|
|
const app = express();
|
|
|
|
app.use(express.json());
|
|
app.use(httpLogger);
|
|
app.use(actorMiddleware(db));
|
|
app.use(llmRoutes(db));
|
|
|
|
// Mount API routes
|
|
const api = Router();
|
|
api.use("/health", healthRoutes());
|
|
api.use("/companies", companyRoutes(db));
|
|
api.use(agentRoutes(db));
|
|
api.use(assetRoutes(db, opts.storageService));
|
|
api.use(projectRoutes(db));
|
|
api.use(issueRoutes(db, opts.storageService));
|
|
api.use(goalRoutes(db));
|
|
api.use(approvalRoutes(db));
|
|
api.use(secretRoutes(db));
|
|
api.use(costRoutes(db));
|
|
api.use(activityRoutes(db));
|
|
api.use(dashboardRoutes(db));
|
|
api.use(sidebarBadgeRoutes(db));
|
|
app.use("/api", api);
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
if (opts.uiMode === "static") {
|
|
// Serve built UI from ui/dist in production.
|
|
const uiDist = path.resolve(__dirname, "../../ui/dist");
|
|
app.use(express.static(uiDist));
|
|
app.get(/.*/, (_req, res) => {
|
|
res.sendFile(path.join(uiDist, "index.html"));
|
|
});
|
|
}
|
|
|
|
if (opts.uiMode === "vite-dev") {
|
|
const uiRoot = path.resolve(__dirname, "../../ui");
|
|
const { createServer: createViteServer } = await import("vite");
|
|
const vite = await createViteServer({
|
|
root: uiRoot,
|
|
appType: "spa",
|
|
server: {
|
|
middlewareMode: true,
|
|
},
|
|
});
|
|
|
|
app.use(vite.middlewares);
|
|
app.get(/.*/, async (req, res, next) => {
|
|
try {
|
|
const templatePath = path.resolve(uiRoot, "index.html");
|
|
const template = fs.readFileSync(templatePath, "utf-8");
|
|
const html = await vite.transformIndexHtml(req.originalUrl, template);
|
|
res.status(200).set({ "Content-Type": "text/html" }).end(html);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|
|
}
|
|
|
|
app.use(errorHandler);
|
|
|
|
return app;
|
|
}
|