From e8cce9655745a70acff251d60dde4bb7aab794d8 Mon Sep 17 00:00:00 2001 From: plenarius Date: Mon, 20 Apr 2026 19:56:22 -0400 Subject: [PATCH] feat: add SQL console endpoint for local MariaDB queries --- packages/backend/src/routes/server.ts | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/packages/backend/src/routes/server.ts b/packages/backend/src/routes/server.ts index 8ac4188..1ee69b3 100644 --- a/packages/backend/src/routes/server.ts +++ b/packages/backend/src/routes/server.ts @@ -7,6 +7,7 @@ import { generateServerConfig, seedDatabase, } from "../services/server.service.js"; +import { getDockerClient } from "../services/docker.service.js"; const router = Router(); @@ -67,4 +68,73 @@ router.post("/seed-db", async (req, res) => { } }); +router.post("/sql", async (req, res) => { + const { query, allowWrite } = req.body; + if (!query) return res.status(400).json({ error: "query required" }); + + const upperQuery = query.trim().toUpperCase(); + if ( + !allowWrite && + (upperQuery.startsWith("DROP") || + upperQuery.startsWith("TRUNCATE") || + upperQuery.startsWith("ALTER")) + ) { + return res + .status(400) + .json({ error: "Destructive query blocked. Set allowWrite: true to override." }); + } + + try { + const docker = getDockerClient(); + const container = docker.getContainer("layonara-mariadb"); + + const exec = await container.exec({ + Cmd: [ + "mysql", + "-u", + "nwn", + `-p${process.env.MYSQL_PASSWORD || "forge"}`, + "nwn", + "-e", + query, + ], + AttachStdout: true, + AttachStderr: true, + }); + + const stream = await exec.start({}); + let output = ""; + + await new Promise((resolve) => { + stream.on("data", (chunk: Buffer) => { + output += chunk.toString(); + }); + stream.on("end", resolve); + }); + + const lines = output + .trim() + .split("\n") + .filter((l) => l.trim()); + if (lines.length === 0) { + res.json({ columns: [], rows: [] }); + return; + } + + const columns = lines[0].split("\t"); + const rows = lines.slice(1).map((line) => { + const values = line.split("\t"); + const row: Record = {}; + columns.forEach((col, i) => { + row[col] = values[i] || ""; + }); + return row; + }); + + res.json({ columns, rows }); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } +}); + export default router;