Compare commits

...

7 Commits

Author SHA1 Message Date
9048c03e81 2 2026-03-19 15:35:07 +07:00
1f150bb83c 123412412412412 2026-03-19 15:34:08 +07:00
64711bc54a 333332 2026-03-19 15:25:55 +07:00
6cb4f53410 Повторение переменонй 2026-03-19 15:10:57 +07:00
7c9c5a44d8 12 2026-03-19 15:07:51 +07:00
2667e86dad фикс игноринга 2026-03-19 15:00:07 +07:00
678db3f1b8 изменения в npm 2026-03-19 14:56:06 +07:00
7 changed files with 1531 additions and 652 deletions

View File

@@ -9,18 +9,6 @@
.gitignore .gitignore
.pre-commit-config.yaml .pre-commit-config.yaml
# ============================================================================
# Node.js / npm
# ============================================================================
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
pnpm-lock.yaml
yarn.lock
package-lock.json
# ============================================================================ # ============================================================================
# IDE & Editor # IDE & Editor
# ============================================================================ # ============================================================================

25
.env
View File

@@ -1,25 +0,0 @@
# =========================
# Server
# =========================
PORT=3000
NODE_ENV=development
# =========================
# Admin panel credentials
# =========================
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
# =========================
# PostgreSQL (Docker)
# =========================
DB_HOST=postgres
DB_PORT=5432
DB_NAME=testdb
DB_USER=postgres
DB_PASSWORD=postgres
# =========================
# Session
# =========================
SESSION_SECRET=super_secret_key_change_this_123

View File

@@ -48,7 +48,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000 || exit 1 CMD curl -f http://localhost:3000 || exit 1
# Use dumb-init to handle signals properly (graceful shutdown) # Use dumb-init to handle signals properly (graceful shutdown)
ENTRYPOINT ["/usr/sbin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
# Start application # Start application
CMD ["node", "server.js"] CMD ["node", "server.js"]

View File

@@ -1,5 +1,3 @@
version: '3.9'
# ============================================================================ # ============================================================================
# PostgreSQL Admin Panel - Complete Stack # PostgreSQL Admin Panel - Complete Stack
# ============================================================================ # ============================================================================

1456
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

664
server.js
View File

@@ -1,707 +1,147 @@
require('dotenv').config(); require('dotenv').config();
const express = require('express'); const express = require('express');
const { Pool } = require('pg'); const { Pool } = require('pg');
const session = require('express-session'); const session = require('express-session');
const cors = require('cors'); const cors = require('cors');
const path = require('path'); const path = require('path');
const fs = require('fs');
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
// Initialize Express app // ======================
// App init
// ======================
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
// ======================
// Middleware // Middleware
// ======================
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
// Session configuration // ======================
// Session
// ======================
app.use(session({ app.use(session({
secret: process.env.SESSION_SECRET || 'default-secret-change-this', secret: process.env.SESSION_SECRET || 'change-this-secret',
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
cookie: { cookie: {
secure: process.env.NODE_ENV === 'production', secure: process.env.NODE_ENV === 'production',
httpOnly: true, httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days maxAge: 1000 * 60 * 60 * 24 * 7
} }
})); }));
// Database connection pool // ======================
// Database
// ======================
const pool = new Pool({ const pool = new Pool({
host: process.env.DB_HOST || 'localhost', host: process.env.DB_HOST || 'postgres', // важно для Docker
port: process.env.DB_PORT || 5432, port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'postgres', database: process.env.DB_NAME || 'postgres',
user: process.env.DB_USER || 'postgres', user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres', password: process.env.DB_PASSWORD || 'postgres',
}); });
// Test database connection // Проверка подключения
pool.connect((err, client, release) => { pool.connect((err, client, release) => {
if (err) { if (err) {
console.error('❌ Error connecting to PostgreSQL:', err.message); console.error('❌ PostgreSQL connection error:', err.message);
console.log('Проверьте настройки в .env файле');
} else { } else {
console.log('✅ Connected to PostgreSQL database'); console.log('✅ PostgreSQL connected');
console.log(` Host: ${process.env.DB_HOST || 'localhost'}:${process.env.DB_PORT || 5432}`);
console.log(` Database: ${process.env.DB_NAME || 'postgres'}`);
release(); release();
} }
}); });
// Initialize database schema // ======================
// Init DB
// ======================
async function initializeDatabase() { async function initializeDatabase() {
const client = await pool.connect(); const client = await pool.connect();
try { try {
// Check if users table exists, if not create it
await client.query(` await client.query(`
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255),
email VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE,
password VARCHAR(255) NOT NULL, password VARCHAR(255),
role VARCHAR(50) NOT NULL DEFAULT 'viewer', role VARCHAR(50) DEFAULT 'viewer',
active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS activity_logs (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
action VARCHAR(255),
details JSONB,
created_at TIMESTAMP DEFAULT NOW() created_at TIMESTAMP DEFAULT NOW()
); );
CREATE INDEX IF NOT EXISTS idx_activity_logs_user_id ON activity_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at);
`); `);
// Create default admin user if doesn't exist const exists = await client.query(
const adminExists = await client.query(
'SELECT id FROM users WHERE email = $1', 'SELECT id FROM users WHERE email = $1',
['admin@example.com'] ['admin@example.com']
); );
if (adminExists.rows.length === 0) { if (exists.rows.length === 0) {
const hashedPassword = await bcrypt.hash('admin123', 10); const hash = await bcrypt.hash('admin123', 10);
await client.query( await client.query(
`INSERT INTO users (name, email, password, role, active) `INSERT INTO users (name, email, password, role)
VALUES ($1, $2, $3, $4, $5)`, VALUES ($1, $2, $3, $4)`,
['Admin', 'admin@example.com', hashedPassword, 'superadmin', true] ['Admin', 'admin@example.com', hash, 'superadmin']
); );
console.log('✅ Created default admin user: admin@example.com / admin123'); console.log('✅ Default admin created');
} }
console.log('✅ Database schema initialized'); } catch (err) {
} catch (error) { console.error('DB init error:', err);
console.error('Database initialization error:', error);
} finally { } finally {
client.release(); client.release();
} }
} }
// Initialize on startup
initializeDatabase(); initializeDatabase();
// ======================
// Routes // Routes
// ======================
const authRoutes = require('./src/routes/auth'); const authRoutes = require('./src/routes/auth');
const userRoutes = require('./src/routes/users'); const userRoutes = require('./src/routes/users');
const dbRoutes = require('./src/routes/db-tables'); const dbRoutes = require('./src/routes/db-tables');
const adminRoutes = require('./src/routes/admin'); const adminRoutes = require('./src/routes/admin');
// API Routes
app.use('/api/auth', authRoutes(pool)); app.use('/api/auth', authRoutes(pool));
app.use('/api/users', userRoutes(pool)); app.use('/api/users', userRoutes(pool));
app.use('/api/db', dbRoutes(pool)); app.use('/api/db', dbRoutes(pool));
app.use('/api/admin', adminRoutes(pool)); app.use('/api/admin', adminRoutes(pool));
// SPA catch-all route // ======================
// SPA fallback
// ======================
app.get('*', (req, res) => { app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html')); res.sendFile(path.join(__dirname, 'public', 'index.html'));
}); });
// Error handling middleware // ======================
// Error handler
// ======================
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
console.error('Error:', err); console.error(err);
res.status(500).json({ res.status(500).json({
success: false, error: 'Internal server error'
message: 'Internal server error',
error: process.env.NODE_ENV === 'development' ? err.message : undefined
}); });
}); });
// ======================
// Start server // Start server
app.listen(PORT, () => { // ======================
console.log(`\n🚀 PostgreSQL Admin Panel running at http://localhost:${PORT}`); app.listen(PORT, '0.0.0.0', () => {
console.log(`📦 Environment: ${process.env.NODE_ENV || 'development'}`); console.log(`🚀 Server running on http://localhost:${PORT}`);
console.log(`\n💾 Database Connection:`);
console.log(` Host: ${process.env.DB_HOST || 'localhost'}`);
console.log(` Database: ${process.env.DB_NAME || 'postgres'}`);
console.log('\n');
}); });
// ======================
// Graceful shutdown // Graceful shutdown
// ======================
process.on('SIGTERM', () => { process.on('SIGTERM', () => {
console.log('SIGTERM received. Shutting down gracefully...'); console.log('SIGTERM received. Shutting down...');
pool.end(() => { pool.end(() => {
console.log('Connection pool closed'); console.log('DB pool closed');
process.exit(0); process.exit(0);
}); });
}); });
module.exports = app; module.exports = app;
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
// Try users.json first
const user = getUser(username);
if (user && password === user.password) {
try {
const client = await pool.connect();
const result = await client.query('SELECT NOW() as time');
client.release();
req.session.authenticated = true;
req.session.username = username;
req.session.role = user.role;
res.json({
success: true,
message: 'Login successful',
role: user.role,
dbInfo: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
connected: true,
serverTime: result.rows[0].time
}
});
return;
} catch (err) {
res.status(500).json({
success: false,
error: 'Database connection failed',
details: err.message
});
return;
}
}
// Fallback to env-based admin
if (username === process.env.ADMIN_USERNAME && password === process.env.ADMIN_PASSWORD) {
try {
const client = await pool.connect();
const result = await client.query('SELECT NOW() as time');
client.release();
req.session.authenticated = true;
req.session.username = username;
req.session.role = 'superadmin';
res.json({
success: true,
message: 'Login successful',
role: 'superadmin',
dbInfo: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
connected: true,
serverTime: result.rows[0].time
}
});
return;
} catch (err) {
res.status(500).json({
success: false,
error: 'Database connection failed',
details: err.message
});
return;
}
}
res.status(401).json({
success: false,
error: 'Invalid credentials'
});
});
// Logout
app.post('/api/logout', (req, res) => {
req.session.destroy();
res.json({ success: true });
});
// Check session
app.get('/api/session', (req, res) => {
if (req.session.authenticated) {
res.json({
authenticated: true,
username: req.session.username,
role: req.session.role || 'viewer',
permissions: getRolePermissions(req.session.role),
dbInfo: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME
}
});
} else {
res.json({ authenticated: false });
}
});
// Get all tables
app.get('/api/tables', requireAuth, async (req, res) => {
try {
const role = req.session.role || 'viewer';
const result = await pool.query(`
SELECT
table_name as name,
(SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name) as column_count
FROM information_schema.tables t
WHERE table_schema = 'public'
ORDER BY table_name
`);
// Filter tables by access rights (folders)
const accessibleTables = result.rows.filter(table => canAccessTable(role, table.name));
// Get row counts for each table
const tablesWithCounts = await Promise.all(
accessibleTables.map(async (table) => {
try {
const countResult = await pool.query(`SELECT COUNT(*) as count FROM "${table.name}"`);
return {
...table,
rows: parseInt(countResult.rows[0].count),
size: 'calculating...' // Would need pg_size_pretty for real size
};
} catch (e) {
return { ...table, rows: 0, size: '0 KB' };
}
})
);
res.json(tablesWithCounts);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get table data with pagination, search, filters and sort
app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
const { tableName } = req.params;
const role = req.session.role || 'viewer';
// Check access for this table
if (!canAccessTable(role, tableName)) {
return res.status(403).json({ error: 'Access denied' });
}
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const search = req.query.search || '';
const filters = req.query.filters ? JSON.parse(req.query.filters) : [];
const sortColumn = req.query.sortColumn || '';
const sortDirection = req.query.sortDirection || 'ASC';
const offset = (page - 1) * limit;
try {
let whereClause = '';
let params = [];
let paramIndex = 1;
// Search
if (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 $${paramIndex}`).join(' OR ');
whereClause = `WHERE ${searchConditions}`;
params.push(`%${search}%`);
paramIndex++;
}
// Filters
if (filters && typeof filters === 'object') {
const filterConditions = Object.entries(filters).map(([column, value]) => {
if (value && value.trim()) {
params.push(`%${value}%`);
paramIndex++;
// Use CAST to TEXT to support UUID and other non-text column types
return `CAST("${column}" AS TEXT) ILIKE $${paramIndex - 1}`;
}
return null;
}).filter(c => c);
if (filterConditions.length > 0) {
whereClause = whereClause ? `${whereClause} AND ${filterConditions.join(' AND ')}` : `WHERE ${filterConditions.join(' AND ')}`;
}
}
// Get total count
const countResult = await pool.query(`SELECT COUNT(*) as total FROM "${tableName}" ${whereClause}`, params);
const total = parseInt(countResult.rows[0].total);
// Build ORDER BY
let orderBy = 'ORDER BY (SELECT NULL)'; // Default no order
if (sortColumn) {
orderBy = `ORDER BY "${sortColumn}" ${sortDirection}`;
}
// Get data
const result = await pool.query(`
SELECT * FROM "${tableName}"
${whereClause}
${orderBy}
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`, [...params, limit, offset]);
res.json({
data: result.rows,
total,
page,
limit,
totalPages: Math.ceil(total / limit)
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get table structure
app.get('/api/tables/:tableName/structure', requireAuth, async (req, res) => {
const { tableName } = req.params;
try {
const result = await pool.query(`
SELECT
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);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Create table
app.post('/api/tables', requireAuth, async (req, res) => {
const { name, columns } = req.body;
const role = req.session.role || 'viewer';
const folder = getTableFolder(name);
const perms = getRolePermissions(role);
if (!perms.canCreate || (perms.folders && !perms.folders.includes(folder))) {
return res.status(403).json({ error: 'Access denied' });
}
try {
let columnsSQL = columns.map(col => {
let def = `"${col.name}" ${col.type}`;
if (col.pk) def += ' PRIMARY KEY';
if (!col.nullable && !col.pk) def += ' NOT NULL';
return def;
}).join(', ');
const sql = `CREATE TABLE "${name}" (${columnsSQL})`;
await pool.query(sql);
res.json({ success: true, message: 'Table created' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Delete table
app.delete('/api/tables/:tableName', requireAuth, async (req, res) => {
const { tableName } = req.params;
const role = req.session.role || 'viewer';
const folder = getTableFolder(tableName);
const perms = getRolePermissions(role);
if (!perms.canDelete || (perms.folders && !perms.folders.includes(folder))) {
return res.status(403).json({ error: 'Access denied' });
}
try {
await pool.query(`DROP TABLE IF EXISTS "${tableName}"`);
res.json({ success: true, message: 'Table deleted' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Insert record
app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => {
const { tableName } = req.params;
const data = req.body;
const role = req.session.role || 'viewer';
const folder = getTableFolder(tableName);
const perms = getRolePermissions(role);
if (!perms.canEdit || (perms.folders && !perms.folders.includes(folder))) {
return res.status(403).json({ error: 'Access denied' });
}
try {
// Get table structure to check types
const structureResult = await pool.query(`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = $1 AND table_schema = 'public'
`, [tableName]);
const structure = structureResult.rows;
// Filter out empty UUIDs and generate if needed
const filteredData = {};
for (const [key, value] of Object.entries(data)) {
const colInfo = structure.find(col => col.column_name === key);
if (colInfo && colInfo.data_type === 'uuid') {
if (!value || value.trim() === '') {
// Generate UUID for empty UUID fields
filteredData[key] = require('crypto').randomUUID();
} else {
// Validate UUID
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (uuidRegex.test(value)) {
filteredData[key] = value;
} else {
// Invalid UUID, generate new one
filteredData[key] = require('crypto').randomUUID();
}
}
} else if (value !== '') {
filteredData[key] = value;
}
}
const columns = Object.keys(filteredData);
const values = Object.values(filteredData);
const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
const sql = `INSERT INTO "${tableName}" (${columns.map(c => `"${c}"`).join(', ')}) VALUES (${placeholders}) RETURNING *`;
const result = await pool.query(sql, values);
res.json({ success: true, data: result.rows[0] });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Update record
app.put('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
const { tableName, pk } = req.params;
const role = req.session.role || 'viewer';
const folder = getTableFolder(tableName);
const perms = getRolePermissions(role);
if (!perms.canEdit || (perms.folders && !perms.folders.includes(folder))) {
return res.status(403).json({ error: 'Access denied' });
}
const data = req.body;
const columns = Object.keys(data);
const values = Object.values(data);
const setClause = columns.map((col, i) => `"${col}" = $${i + 1}`).join(', ');
try {
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 });
}
});
// Delete record
app.delete('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
const { tableName, pk } = req.params;
const role = req.session.role || 'viewer';
const folder = getTableFolder(tableName);
const perms = getRolePermissions(role);
if (!perms.canDelete || (perms.folders && !perms.folders.includes(folder))) {
return res.status(403).json({ error: 'Access denied' });
}
try {
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 });
}
});
// Execute SQL
app.post('/api/query', requireAuth, async (req, res) => {
const { sql } = req.body;
try {
const result = await pool.query(sql);
res.json({
success: true,
rows: result.rows,
rowCount: result.rowCount,
command: result.command
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get indexes
app.get('/api/tables/:tableName/indexes', requireAuth, async (req, res) => {
const { tableName } = req.params;
try {
const result = await pool.query(`
SELECT
indexname as name,
indexdef as definition
FROM pg_indexes
WHERE tablename = $1
`, [tableName]);
const indexes = result.rows.map(row => ({
name: row.name,
columns: row.definition.match(/\((.*?)\)/)?.[1] || 'unknown',
unique: row.definition.includes('UNIQUE'),
type: 'btree'
}));
res.json(indexes);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Create index
app.post('/api/tables/:tableName/indexes', requireAuth, async (req, res) => {
const { tableName } = req.params;
const { name, columns, unique } = req.body;
try {
const uniqueStr = unique ? 'UNIQUE' : '';
const sql = `CREATE ${uniqueStr} INDEX "${name}" ON "${tableName}" (${columns})`;
await pool.query(sql);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Drop index
app.delete('/api/indexes/:indexName', requireAuth, async (req, res) => {
const { indexName } = req.params;
try {
await pool.query(`DROP INDEX IF EXISTS "${indexName}"`);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running on http://localhost:${PORT}`);
console.log('');
console.log('🔑 Default login credentials:');
console.log(` Username: ${process.env.ADMIN_USERNAME || 'admin'}`);
console.log(` Password: ${process.env.ADMIN_PASSWORD || 'admin'}`);
console.log('');
console.log('📝 Make sure to configure your database in .env file');
});

22
src/middleware/auth.js Normal file
View File

@@ -0,0 +1,22 @@
// Authentication middleware
function requireAuth(req, res, next) {
if (!req.session || !req.session.authenticated) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
// Role-based permissions
function getRolePermissions(role) {
const permissions = {
admin: ['read', 'write', 'delete', 'admin'],
editor: ['read', 'write'],
viewer: ['read']
};
return permissions[role] || permissions.viewer;
}
module.exports = {
requireAuth,
getRolePermissions
};