213124124
This commit is contained in:
1023
index.html
1023
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
|
||||
const requireAuth = (req, res, next) => {
|
||||
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) => {
|
||||
const { tableName } = req.params;
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const search = req.query.search || '';
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
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
|
||||
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);
|
||||
|
||||
// Get data
|
||||
const result = await pool.query(`
|
||||
SELECT * FROM "${tableName}"
|
||||
${whereClause}
|
||||
ORDER BY (SELECT NULL) -- No specific order, but consistent
|
||||
LIMIT $1 OFFSET $2
|
||||
`, [limit, offset]);
|
||||
`, params);
|
||||
|
||||
res.json({
|
||||
data: result.rows,
|
||||
@@ -186,13 +224,22 @@ app.get('/api/tables/:tableName/structure', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
column_name as name,
|
||||
data_type as type,
|
||||
is_nullable as nullable,
|
||||
column_default as default_value
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = $1 AND table_schema = 'public'
|
||||
ORDER BY ordinal_position
|
||||
c.column_name as name,
|
||||
c.data_type as type,
|
||||
c.is_nullable as nullable,
|
||||
c.column_default as default_value,
|
||||
CASE WHEN kcu.column_name IS NOT NULL THEN true ELSE false END as is_primary
|
||||
FROM information_schema.columns c
|
||||
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]);
|
||||
|
||||
res.json(result.rows);
|
||||
@@ -253,8 +300,8 @@ app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
// Update record
|
||||
app.put('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
||||
const { tableName, id } = req.params;
|
||||
app.put('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
||||
const { tableName, pk } = req.params;
|
||||
const data = req.body;
|
||||
|
||||
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(', ');
|
||||
|
||||
try {
|
||||
// Assuming id column is named 'id' - in production you'd need to detect PK
|
||||
const sql = `UPDATE "${tableName}" SET ${setClause} WHERE id = $${values.length + 1} RETURNING *`;
|
||||
const result = await pool.query(sql, [...values, id]);
|
||||
const primaryKey = await getPrimaryKeyColumn(tableName) || 'id';
|
||||
const sql = `UPDATE "${tableName}" SET ${setClause} WHERE "${primaryKey}" = $${values.length + 1} RETURNING *`;
|
||||
const result = await pool.query(sql, [...values, pk]);
|
||||
res.json({ success: true, data: result.rows[0] });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -272,11 +319,77 @@ app.put('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
// Delete record
|
||||
app.delete('/api/tables/:tableName/records/:id', requireAuth, async (req, res) => {
|
||||
const { tableName, id } = req.params;
|
||||
app.delete('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
||||
const { tableName, pk } = req.params;
|
||||
|
||||
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 });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
|
||||
Reference in New Issue
Block a user