213124124
This commit is contained in:
1009
index.html
1009
index.html
File diff suppressed because it is too large
Load Diff
149
server.js
149
server.js
@@ -42,6 +42,23 @@ pool.connect((err, client, release) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper: get primary key column for a table (returns null if none)
|
||||||
|
async function getPrimaryKeyColumn(tableName) {
|
||||||
|
const result = await pool.query(`
|
||||||
|
SELECT kcu.column_name
|
||||||
|
FROM information_schema.table_constraints tc
|
||||||
|
JOIN information_schema.key_column_usage kcu
|
||||||
|
ON tc.constraint_name = kcu.constraint_name
|
||||||
|
AND tc.table_schema = kcu.table_schema
|
||||||
|
WHERE tc.constraint_type = 'PRIMARY KEY'
|
||||||
|
AND tc.table_name = $1
|
||||||
|
AND tc.table_schema = 'public'
|
||||||
|
LIMIT 1
|
||||||
|
`, [tableName]);
|
||||||
|
|
||||||
|
return result.rows[0]?.column_name || null;
|
||||||
|
}
|
||||||
|
|
||||||
// Middleware to check if user is authenticated
|
// Middleware to check if user is authenticated
|
||||||
const requireAuth = (req, res, next) => {
|
const requireAuth = (req, res, next) => {
|
||||||
if (req.session && req.session.authenticated) {
|
if (req.session && req.session.authenticated) {
|
||||||
@@ -149,23 +166,44 @@ app.get('/api/tables', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get table data with pagination
|
// Get table data with pagination and search
|
||||||
app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
|
app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
|
||||||
const { tableName } = req.params;
|
const { tableName } = req.params;
|
||||||
const page = parseInt(req.query.page) || 1;
|
const page = parseInt(req.query.page) || 1;
|
||||||
const limit = parseInt(req.query.limit) || 10;
|
const limit = parseInt(req.query.limit) || 10;
|
||||||
|
const search = req.query.search || '';
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let whereClause = '';
|
||||||
|
let params = [limit, offset];
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
// Get column names for search
|
||||||
|
const columnsResult = await pool.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = $1 AND table_schema = 'public'
|
||||||
|
ORDER BY ordinal_position
|
||||||
|
`, [tableName]);
|
||||||
|
|
||||||
|
const columns = columnsResult.rows.map(row => row.column_name);
|
||||||
|
const searchConditions = columns.map(col => `CAST("${col}" AS TEXT) ILIKE $${params.length + 1}`).join(' OR ');
|
||||||
|
whereClause = `WHERE ${searchConditions}`;
|
||||||
|
params.push(`%${search}%`);
|
||||||
|
}
|
||||||
|
|
||||||
// Get total count
|
// Get total count
|
||||||
const countResult = await pool.query(`SELECT COUNT(*) as total FROM "${tableName}"`);
|
const countResult = await pool.query(`SELECT COUNT(*) as total FROM "${tableName}" ${whereClause}`, params.slice(2));
|
||||||
const total = parseInt(countResult.rows[0].total);
|
const total = parseInt(countResult.rows[0].total);
|
||||||
|
|
||||||
// Get data
|
// Get data
|
||||||
const result = await pool.query(`
|
const result = await pool.query(`
|
||||||
SELECT * FROM "${tableName}"
|
SELECT * FROM "${tableName}"
|
||||||
|
${whereClause}
|
||||||
|
ORDER BY (SELECT NULL) -- No specific order, but consistent
|
||||||
LIMIT $1 OFFSET $2
|
LIMIT $1 OFFSET $2
|
||||||
`, [limit, offset]);
|
`, params);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
data: result.rows,
|
data: result.rows,
|
||||||
@@ -186,13 +224,22 @@ app.get('/api/tables/:tableName/structure', requireAuth, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const result = await pool.query(`
|
const result = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
column_name as name,
|
c.column_name as name,
|
||||||
data_type as type,
|
c.data_type as type,
|
||||||
is_nullable as nullable,
|
c.is_nullable as nullable,
|
||||||
column_default as default_value
|
c.column_default as default_value,
|
||||||
FROM information_schema.columns
|
CASE WHEN kcu.column_name IS NOT NULL THEN true ELSE false END as is_primary
|
||||||
WHERE table_name = $1 AND table_schema = 'public'
|
FROM information_schema.columns c
|
||||||
ORDER BY ordinal_position
|
LEFT JOIN information_schema.table_constraints tc
|
||||||
|
ON tc.table_name = c.table_name
|
||||||
|
AND tc.table_schema = c.table_schema
|
||||||
|
AND tc.constraint_type = 'PRIMARY KEY'
|
||||||
|
LEFT JOIN information_schema.key_column_usage kcu
|
||||||
|
ON kcu.constraint_name = tc.constraint_name
|
||||||
|
AND kcu.table_schema = tc.table_schema
|
||||||
|
AND kcu.column_name = c.column_name
|
||||||
|
WHERE c.table_name = $1 AND c.table_schema = 'public'
|
||||||
|
ORDER BY c.ordinal_position
|
||||||
`, [tableName]);
|
`, [tableName]);
|
||||||
|
|
||||||
res.json(result.rows);
|
res.json(result.rows);
|
||||||
@@ -253,8 +300,8 @@ app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update record
|
// Update record
|
||||||
app.put('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
app.put('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
||||||
const { tableName, id } = req.params;
|
const { tableName, pk } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
const columns = Object.keys(data);
|
const columns = Object.keys(data);
|
||||||
@@ -262,9 +309,9 @@ app.put('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
|||||||
const setClause = columns.map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
const setClause = columns.map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Assuming id column is named 'id' - in production you'd need to detect PK
|
const primaryKey = await getPrimaryKeyColumn(tableName) || 'id';
|
||||||
const sql = `UPDATE "${tableName}" SET ${setClause} WHERE id = $${values.length + 1} RETURNING *`;
|
const sql = `UPDATE "${tableName}" SET ${setClause} WHERE "${primaryKey}" = $${values.length + 1} RETURNING *`;
|
||||||
const result = await pool.query(sql, [...values, id]);
|
const result = await pool.query(sql, [...values, pk]);
|
||||||
res.json({ success: true, data: result.rows[0] });
|
res.json({ success: true, data: result.rows[0] });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
@@ -272,11 +319,77 @@ app.put('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Delete record
|
// Delete record
|
||||||
app.delete('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
app.delete('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
||||||
const { tableName, id } = req.params;
|
const { tableName, pk } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pool.query(`DELETE FROM "${tableName}" WHERE id = $1`, [id]);
|
const primaryKey = await getPrimaryKeyColumn(tableName) || 'id';
|
||||||
|
await pool.query(`DELETE FROM "${tableName}" WHERE "${primaryKey}" = $1`, [pk]);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a new column
|
||||||
|
app.post('/api/tables/:tableName/columns', requireAuth, async (req, res) => {
|
||||||
|
const { tableName } = req.params;
|
||||||
|
const { name, type, nullable = true, defaultValue, primaryKey } = req.body;
|
||||||
|
|
||||||
|
if (!name || !type) {
|
||||||
|
return res.status(400).json({ error: 'Column name and type are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = [`"${name}" ${type}`];
|
||||||
|
if (primaryKey) parts.push('PRIMARY KEY');
|
||||||
|
if (!nullable) parts.push('NOT NULL');
|
||||||
|
if (defaultValue !== undefined && defaultValue !== null && defaultValue !== '') {
|
||||||
|
parts.push(`DEFAULT ${defaultValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query(`ALTER TABLE "${tableName}" ADD COLUMN ${parts.join(' ')}`);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modify an existing column
|
||||||
|
app.put('/api/tables/:tableName/columns/:columnName', requireAuth, async (req, res) => {
|
||||||
|
const { tableName, columnName } = req.params;
|
||||||
|
const { type, nullable, defaultValue } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (type) {
|
||||||
|
await pool.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof nullable === 'boolean') {
|
||||||
|
const nullSql = nullable ? 'DROP NOT NULL' : 'SET NOT NULL';
|
||||||
|
await pool.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" ${nullSql}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue !== undefined) {
|
||||||
|
if (defaultValue === null || defaultValue === '') {
|
||||||
|
await pool.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT`);
|
||||||
|
} else {
|
||||||
|
await pool.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DEFAULT ${defaultValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drop a column
|
||||||
|
app.delete('/api/tables/:tableName/columns/:columnName', requireAuth, async (req, res) => {
|
||||||
|
const { tableName, columnName } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query(`ALTER TABLE "${tableName}" DROP COLUMN IF EXISTS "${columnName}"`);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
|
|||||||
Reference in New Issue
Block a user