This commit is contained in:
2026-03-18 17:57:59 +07:00
parent ad117fe837
commit ead769e9d1
3 changed files with 252 additions and 28 deletions

158
server.js
View File

@@ -4,6 +4,44 @@ const { Pool } = require('pg');
const session = require('express-session');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
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);
}
const app = express();
@@ -68,24 +106,26 @@ const requireAuth = (req, res, next) => {
}
};
// Login endpoint - checks admin credentials from .env
// Login endpoint - checks users.json (fallback to .env admin)
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
// Check against .env credentials
if (username === process.env.ADMIN_USERNAME && password === process.env.ADMIN_PASSWORD) {
// Test database connection
// 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,
@@ -94,19 +134,55 @@ app.post('/api/login', async (req, res) => {
serverTime: result.rows[0].time
}
});
return;
} catch (err) {
res.status(500).json({
success: false,
error: 'Database connection failed',
details: err.message
});
return;
}
} else {
res.status(401).json({
success: false,
error: 'Invalid credentials'
});
}
// 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
@@ -121,6 +197,8 @@ app.get('/api/session', (req, res) => {
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,
@@ -135,6 +213,7 @@ app.get('/api/session', (req, res) => {
// 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,
@@ -143,10 +222,13 @@ app.get('/api/tables', requireAuth, async (req, res) => {
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(
result.rows.map(async (table) => {
accessibleTables.map(async (table) => {
try {
const countResult = await pool.query(`SELECT COUNT(*) as count FROM "${table.name}"`);
return {
@@ -169,6 +251,13 @@ app.get('/api/tables', requireAuth, async (req, res) => {
// 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 || '';
@@ -279,7 +368,14 @@ app.get('/api/tables/:tableName/structure', requireAuth, async (req, res) => {
// 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}`;
@@ -300,6 +396,13 @@ app.post('/api/tables', requireAuth, async (req, res) => {
// 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}"`);
@@ -313,7 +416,14 @@ app.delete('/api/tables/:tableName', requireAuth, async (req, res) => {
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(`
@@ -362,8 +472,14 @@ app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => {
// 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(', ');
@@ -381,7 +497,13 @@ app.put('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
// 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]);