177 lines
5.2 KiB
JavaScript
177 lines
5.2 KiB
JavaScript
import express from 'express';
|
|
import http from 'http';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import env from '../config/env.js';
|
|
import { createLogger } from '../util/logger.js';
|
|
import * as dbHelper from './dbHelper.js';
|
|
|
|
const studioLog = createLogger('sqlstudio');
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
let studioServer;
|
|
let studioApp;
|
|
|
|
export function isDevMode() {
|
|
return (
|
|
process.env.npm_lifecycle_event === 'dev' ||
|
|
process.env.DEV_SQL_STUDIO === 'true' ||
|
|
process.env.DEV_MODE === 'true'
|
|
);
|
|
}
|
|
|
|
export function buildStudioApp() {
|
|
const app = express();
|
|
|
|
app.use(express.json({ limit: '50mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
|
|
|
// Unrestricted local CORS
|
|
app.use((req, res, next) => {
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
|
|
if (req.method === 'OPTIONS') {
|
|
return res.sendStatus(204);
|
|
}
|
|
next();
|
|
});
|
|
|
|
// 1. Status
|
|
app.get('/api/status', async (req, res) => {
|
|
try {
|
|
const info = await dbHelper.getDatabaseInfo();
|
|
res.json(info);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 2. Tables list
|
|
app.get('/api/tables', async (req, res) => {
|
|
try {
|
|
const tables = await dbHelper.getTables();
|
|
res.json({ tables });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 3. Table schema
|
|
app.get('/api/tables/:table/schema', async (req, res) => {
|
|
try {
|
|
const schema = await dbHelper.getTableSchema(req.params.table);
|
|
res.json({ tableName: req.params.table, columns: schema });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 4. Table data (browsing)
|
|
app.get('/api/tables/:table/data', async (req, res) => {
|
|
try {
|
|
const data = await dbHelper.getTableData(req.params.table, {
|
|
page: req.query.page,
|
|
pageSize: req.query.pageSize,
|
|
sortBy: req.query.sortBy,
|
|
sortDir: req.query.sortDir,
|
|
search: req.query.search,
|
|
});
|
|
res.json(data);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 5. Insert row
|
|
app.post('/api/tables/:table/row', async (req, res) => {
|
|
try {
|
|
const result = await dbHelper.insertRow(req.params.table, req.body.row || req.body);
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 6. Update row
|
|
app.put('/api/tables/:table/row', async (req, res) => {
|
|
try {
|
|
const { primaryKey, changes } = req.body;
|
|
const result = await dbHelper.updateRow(req.params.table, { primaryKey, changes });
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 7. Delete row
|
|
app.delete('/api/tables/:table/row', async (req, res) => {
|
|
try {
|
|
const { primaryKey } = req.body;
|
|
const result = await dbHelper.deleteRow(req.params.table, { primaryKey });
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(400).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 8. Raw SQL Query execution
|
|
app.post('/api/query', async (req, res) => {
|
|
try {
|
|
const { sql } = req.body;
|
|
const result = await dbHelper.executeRawQuery(sql);
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Static assets
|
|
const publicDir = path.join(__dirname, 'public');
|
|
app.use(express.static(publicDir));
|
|
|
|
// Fallback to index.html for SPA routing
|
|
app.use((req, res) => {
|
|
res.sendFile(path.join(publicDir, 'index.html'));
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
export async function startSqlStudio() {
|
|
if (studioServer) {
|
|
return studioServer;
|
|
}
|
|
|
|
const app = buildStudioApp();
|
|
studioApp = app;
|
|
|
|
const serverInstance = http.createServer(app);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
serverInstance.once('listening', () => resolve());
|
|
serverInstance.once('error', (err) => {
|
|
studioLog.error(`Failed to listen on port ${env.sqlStudioPort}:`, err.message);
|
|
reject(err);
|
|
});
|
|
serverInstance.listen(env.sqlStudioPort);
|
|
});
|
|
|
|
studioServer = serverInstance;
|
|
studioLog.info(`SQL Studio running on http://localhost:${env.sqlStudioPort} (dev mode, no auth)`);
|
|
return studioServer;
|
|
}
|
|
|
|
export async function stopSqlStudio() {
|
|
if (!studioServer) return;
|
|
const s = studioServer;
|
|
studioServer = undefined;
|
|
studioApp = undefined;
|
|
|
|
await new Promise((resolve, reject) => {
|
|
s.close((err) => (err ? reject(err) : resolve()));
|
|
s.closeAllConnections();
|
|
});
|
|
studioLog.info('SQL Studio stopped');
|
|
}
|