app.ts•4.33 kB
import http from "http";
import fs from "fs";
import path from "path";
import { URL } from "url";
import { listConfigs, saveConfig, listHistory, getConfig, DbConfig } from "./storage.js";
import { listTables, previewTable, runSelect } from "./db.js";
const port = Number(process.env.PORT || 4000);
const publicDir = path.join(process.cwd(), "public");
function sendJson(res: http.ServerResponse, status: number, payload: unknown) {
res.writeHead(status, { "Content-Type": "application/json" });
res.end(JSON.stringify(payload));
}
async function readBody(req: http.IncomingMessage): Promise<any> {
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(chunk as Buffer);
}
const raw = Buffer.concat(chunks).toString("utf8");
if (!raw) return {};
return JSON.parse(raw);
}
function serveStatic(req: http.IncomingMessage, res: http.ServerResponse, pathname: string) {
const filePath = path.join(publicDir, pathname === "/" ? "index.html" : pathname.slice(1));
if (!filePath.startsWith(publicDir)) {
res.writeHead(403);
return res.end("Forbidden");
}
if (!fs.existsSync(filePath)) {
res.writeHead(404);
return res.end("Not found");
}
const content = fs.readFileSync(filePath);
const ext = path.extname(filePath);
const contentType =
ext === ".html"
? "text/html"
: ext === ".js"
? "text/javascript"
: ext === ".css"
? "text/css"
: "application/octet-stream";
res.writeHead(200, { "Content-Type": contentType });
res.end(content);
}
const server = http.createServer(async (req, res) => {
if (!req.url || !req.method) return;
const url = new URL(req.url, `http://localhost:${port}`);
if (url.pathname.startsWith("/api/")) {
try {
if (req.method === "GET" && url.pathname === "/api/configs") {
return sendJson(res, 200, listConfigs());
}
if (req.method === "POST" && url.pathname === "/api/configs") {
const body = await readBody(req);
const config: DbConfig = {
name: body.name,
kind: body.kind || "postgres",
host: body.host,
port: Number(body.port),
user: body.user,
password: body.password,
database: body.database,
ssl: body.ssl !== undefined ? Boolean(body.ssl) : undefined,
sslMode: body.sslMode || undefined,
};
if (!config.name) throw new Error("Config name is required");
saveConfig(config);
return sendJson(res, 201, { ok: true });
}
if (req.method === "GET" && url.pathname === "/api/history") {
return sendJson(res, 200, listHistory());
}
if (req.method === "GET" && url.pathname === "/api/tables") {
const dbName = url.searchParams.get("db");
if (!dbName) throw new Error("Missing db");
const config = getConfig(dbName);
if (!config) throw new Error(`Unknown db config: ${dbName}`);
const tables = await listTables(config);
return sendJson(res, 200, tables);
}
if (req.method === "GET" && url.pathname === "/api/preview") {
const dbName = url.searchParams.get("db");
const table = url.searchParams.get("table");
if (!dbName || !table) throw new Error("Missing db or table");
const config = getConfig(dbName);
if (!config) throw new Error(`Unknown db config: ${dbName}`);
const data = await previewTable(config, table);
return sendJson(res, 200, data);
}
if (req.method === "POST" && url.pathname === "/api/select") {
const body = await readBody(req);
const dbName = body.db as string;
const sql = body.sql as string;
if (!dbName || !sql) throw new Error("Missing db or sql");
const config = getConfig(dbName);
if (!config) throw new Error(`Unknown db config: ${dbName}`);
const rows = await runSelect(config, sql);
return sendJson(res, 200, rows);
}
res.writeHead(404);
return res.end("Not found");
} catch (err) {
console.error(err);
return sendJson(res, 400, { error: (err as Error).message });
}
}
serveStatic(req, res, url.pathname);
});
server.listen(port, () => {
console.log(`UI/API server running at http://localhost:${port}`);
});