213124124

This commit is contained in:
2026-03-18 16:56:33 +07:00
parent 1a672f0908
commit c9b8be54c5
2 changed files with 665 additions and 507 deletions

1023
index.html

File diff suppressed because it is too large Load Diff

149
server.js
View File

@@ -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 });