This commit is contained in:
2026-03-19 14:36:35 +07:00
parent 6d7d86befd
commit 96635dbcf2
28 changed files with 4332 additions and 1683 deletions

188
server.js
View File

@@ -5,108 +5,154 @@ const session = require('express-session');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const bcrypt = require('bcryptjs');
let usersConfig = { users: [] };
try {
usersConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'users.json'), 'utf8'));
} catch (err) {
console.warn('⚠️ users.json not found or invalid JSON. Falling back to env-based admin only.');
}
const rolePermissions = {
superadmin: { folders: null, canCreate: true, canEdit: true, canDelete: true },
frontend_admin: { folders: ['frontend'], canCreate: true, canEdit: true, canDelete: true },
backend_admin: { folders: ['backend'], canCreate: true, canEdit: true, canDelete: true },
frontend_moder: { folders: ['frontend'], canCreate: true, canEdit: true, canDelete: false },
backend_moder: { folders: ['backend'], canCreate: true, canEdit: true, canDelete: false },
viewer: { folders: null, canCreate: false, canEdit: false, canDelete: false },
};
function getUser(username) {
return usersConfig.users.find(u => u.username === username);
}
function getTableFolder(tableName) {
if (!tableName) return 'default';
const parts = tableName.split('__');
return parts.length > 1 ? parts[0] : 'default';
}
function getRolePermissions(role) {
return rolePermissions[role] || rolePermissions.viewer;
}
function canAccessTable(role, tableName) {
const perms = getRolePermissions(role);
if (!perms.folders) return true;
const folder = getTableFolder(tableName);
return perms.folders.includes(folder);
}
// Initialize Express app
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('.'));
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: process.env.SESSION_SECRET || 'default-secret-change-this',
resave: false,
saveUninitialized: false,
cookie: { secure: false } // Set to true if using HTTPS
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days
}
}));
// Database connection pool (uses .env configuration)
// Database connection pool
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'postgres',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
});
// Test database connection on startup
// Test database connection
pool.connect((err, client, release) => {
if (err) {
console.error('❌ Error connecting to PostgreSQL:', err.message);
console.log('Проверьте настройки в .env файле');
} else {
console.log('✅ Connected to PostgreSQL database');
console.log(` Host: ${process.env.DB_HOST}:${process.env.DB_PORT}`);
console.log(` Database: ${process.env.DB_NAME}`);
console.log(` Host: ${process.env.DB_HOST || 'localhost'}:${process.env.DB_PORT || 5432}`);
console.log(` Database: ${process.env.DB_NAME || 'postgres'}`);
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]);
// Initialize database schema
async function initializeDatabase() {
const client = await pool.connect();
try {
// Check if users table exists, if not create it
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'viewer',
active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
return result.rows[0]?.column_name || null;
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()
);
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 adminExists = await client.query(
'SELECT id FROM users WHERE email = $1',
['admin@example.com']
);
if (adminExists.rows.length === 0) {
const hashedPassword = await bcrypt.hash('admin123', 10);
await client.query(
`INSERT INTO users (name, email, password, role, active)
VALUES ($1, $2, $3, $4, $5)`,
['Admin', 'admin@example.com', hashedPassword, 'superadmin', true]
);
console.log('✅ Created default admin user: admin@example.com / admin123');
}
console.log('✅ Database schema initialized');
} catch (error) {
console.error('Database initialization error:', error);
} finally {
client.release();
}
}
// Middleware to check if user is authenticated
const requireAuth = (req, res, next) => {
if (req.session && req.session.authenticated) {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
// Initialize on startup
initializeDatabase();
// Login endpoint - checks users.json (fallback to .env admin)
// Routes
const authRoutes = require('./src/routes/auth');
const userRoutes = require('./src/routes/users');
const dbRoutes = require('./src/routes/db-tables');
const adminRoutes = require('./src/routes/admin');
// API Routes
app.use('/api/auth', authRoutes(pool));
app.use('/api/users', userRoutes(pool));
app.use('/api/db', dbRoutes(pool));
app.use('/api/admin', adminRoutes(pool));
// SPA catch-all route
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({
success: false,
message: 'Internal server error',
error: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});
// Start server
app.listen(PORT, () => {
console.log(`\n🚀 PostgreSQL Admin Panel running at http://localhost:${PORT}`);
console.log(`📦 Environment: ${process.env.NODE_ENV || 'development'}`);
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
process.on('SIGTERM', () => {
console.log('SIGTERM received. Shutting down gracefully...');
pool.end(() => {
console.log('Connection pool closed');
process.exit(0);
});
});
module.exports = app;
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;