adminka
This commit is contained in:
88
index.html
88
index.html
@@ -257,6 +257,11 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 overflow-y-auto flex-1">
|
<div class="p-6 overflow-y-auto flex-1">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-slate-700 mb-1">Папка</label>
|
||||||
|
<input type="text" id="newTableFolder" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="frontend">
|
||||||
|
<p class="text-xs text-slate-500 mt-1">Если указано, имя таблицы будет создано как <code>папка__имя</code>. Оставьте пустым для создания в корне.</p>
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-slate-700 mb-1">Название таблицы</label>
|
<label class="block text-sm font-medium text-slate-700 mb-1">Название таблицы</label>
|
||||||
<input type="text" id="newTableName" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="users">
|
<input type="text" id="newTableName" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="users">
|
||||||
@@ -510,6 +515,8 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.currentUser = {
|
this.currentUser = {
|
||||||
username,
|
username,
|
||||||
|
role: data.role,
|
||||||
|
permissions: data.permissions,
|
||||||
dbInfo: data.dbInfo
|
dbInfo: data.dbInfo
|
||||||
};
|
};
|
||||||
localStorage.setItem('pg_admin_session', JSON.stringify(this.currentUser));
|
localStorage.setItem('pg_admin_session', JSON.stringify(this.currentUser));
|
||||||
@@ -545,6 +552,37 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
this.loadTables();
|
this.loadTables();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPermissions() {
|
||||||
|
return this.currentUser?.permissions || { folders: null, canCreate: false, canEdit: false, canDelete: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTableFolder() {
|
||||||
|
if (!this.currentTable) return null;
|
||||||
|
const parts = this.currentTable.split('__');
|
||||||
|
return parts.length > 1 ? parts[0] : 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
canCreate() {
|
||||||
|
const perms = this.getPermissions();
|
||||||
|
return perms.canCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
canEditTable() {
|
||||||
|
const perms = this.getPermissions();
|
||||||
|
const folder = this.getCurrentTableFolder();
|
||||||
|
if (!perms.canEdit) return false;
|
||||||
|
if (!perms.folders) return true;
|
||||||
|
return perms.folders.includes(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
canDeleteTable() {
|
||||||
|
const perms = this.getPermissions();
|
||||||
|
const folder = this.getCurrentTableFolder();
|
||||||
|
if (!perms.canDelete) return false;
|
||||||
|
if (!perms.folders) return true;
|
||||||
|
return perms.folders.includes(folder);
|
||||||
|
}
|
||||||
|
|
||||||
// Data Loading
|
// Data Loading
|
||||||
async loadTables() {
|
async loadTables() {
|
||||||
try {
|
try {
|
||||||
@@ -563,20 +601,38 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
renderTableList() {
|
renderTableList() {
|
||||||
const container = document.getElementById('tableList');
|
const container = document.getElementById('tableList');
|
||||||
const search = document.getElementById('tableSearch').value.toLowerCase();
|
const search = document.getElementById('tableSearch').value.toLowerCase();
|
||||||
|
|
||||||
container.innerHTML = this.tables
|
const filtered = this.tables.filter(t => t.name.toLowerCase().includes(search));
|
||||||
.filter(t => t.name.toLowerCase().includes(search))
|
const grouped = filtered.reduce((acc, table) => {
|
||||||
.map(table => `
|
const parts = table.name.split('__');
|
||||||
|
const folder = parts.length > 1 ? parts[0] : 'default';
|
||||||
|
if (!acc[folder]) acc[folder] = [];
|
||||||
|
acc[folder].push(table);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const folderOrder = Object.keys(grouped).sort();
|
||||||
|
container.innerHTML = folderOrder.map(folder => {
|
||||||
|
const label = folder === 'default' ? 'Общие' : folder;
|
||||||
|
const tablesHtml = grouped[folder].map(table => `
|
||||||
<div onclick="app.selectTable('${table.name}')"
|
<div onclick="app.selectTable('${table.name}')"
|
||||||
class="sidebar-item px-4 py-3 cursor-pointer border-l-2 ${this.currentTable === table.name ? 'border-blue-500 bg-slate-800/50 text-white' : 'border-transparent hover:text-white'} transition-all flex items-center justify-between group">
|
class="sidebar-item px-4 py-3 cursor-pointer border-l-2 ${this.currentTable === table.name ? 'border-blue-500 bg-slate-800/50 text-white' : 'border-transparent hover:text-white'} transition-all flex items-center justify-between group">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<i data-lucide="table-2" class="w-4 h-4 opacity-70"></i>
|
<i data-lucide="table-2" class="w-4 h-4 opacity-70"></i>
|
||||||
<span class="text-sm font-medium">${table.name}</span>
|
<span class="text-sm font-medium">${table.name.replace(`${folder}__`, '')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs opacity-50 group-hover:opacity-100">${table.rows}</span>
|
<span class="text-xs opacity-50 group-hover:opacity-100">${table.rows}</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="px-4 py-2 text-xs text-slate-400 uppercase tracking-wider font-semibold">${label}</div>
|
||||||
|
${tablesHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,6 +650,12 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
document.getElementById('emptyState').classList.add('hidden');
|
document.getElementById('emptyState').classList.add('hidden');
|
||||||
document.getElementById('sqlPanel').classList.add('hidden');
|
document.getElementById('sqlPanel').classList.add('hidden');
|
||||||
document.getElementById('dataGrid').classList.remove('hidden');
|
document.getElementById('dataGrid').classList.remove('hidden');
|
||||||
|
|
||||||
|
// Update action buttons based on permissions
|
||||||
|
const addBtn = document.querySelector('#tableActions button[onclick="app.showAddRecordModal()"]');
|
||||||
|
const deleteTableBtn = document.querySelector('#tableActions button[onclick="app.deleteTable()"]');
|
||||||
|
if (addBtn) addBtn.style.display = this.canEditTable() ? '' : 'none';
|
||||||
|
if (deleteTableBtn) deleteTableBtn.style.display = this.canDeleteTable() ? '' : 'none';
|
||||||
|
|
||||||
// Load table structure to get primary key
|
// Load table structure to get primary key
|
||||||
await this.loadTableStructure();
|
await this.loadTableStructure();
|
||||||
@@ -658,10 +720,12 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
}
|
}
|
||||||
return `<td class="p-3 border-b border-slate-100">${displayValue}</td>`;
|
return `<td class="p-3 border-b border-slate-100">${displayValue}</td>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
const canEdit = this.canEditTable();
|
||||||
|
const canDelete = this.canDeleteTable();
|
||||||
const actions = pkValue ? `
|
const actions = pkValue ? `
|
||||||
<td class="p-3 border-b border-slate-100">
|
<td class="p-3 border-b border-slate-100">
|
||||||
<button onclick="app.editRecord('${pkValue}')" class="text-blue-600 hover:underline mr-2">Редактировать</button>
|
${canEdit ? `<button onclick="app.editRecord('${pkValue}')" class="text-blue-600 hover:underline mr-2">Редактировать</button>` : ''}
|
||||||
<button onclick="app.deleteRecord('${pkValue}')" class="text-red-600 hover:underline">Удалить</button>
|
${canDelete ? `<button onclick="app.deleteRecord('${pkValue}')" class="text-red-600 hover:underline">Удалить</button>` : ''}
|
||||||
</td>
|
</td>
|
||||||
` : '<td class="p-3 border-b border-slate-100">-</td>';
|
` : '<td class="p-3 border-b border-slate-100">-</td>';
|
||||||
|
|
||||||
@@ -986,6 +1050,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
}
|
}
|
||||||
|
|
||||||
showCreateTableModal() {
|
showCreateTableModal() {
|
||||||
|
document.getElementById('newTableFolder').value = '';
|
||||||
document.getElementById('newTableName').value = '';
|
document.getElementById('newTableName').value = '';
|
||||||
document.getElementById('columnsContainer').innerHTML = '';
|
document.getElementById('columnsContainer').innerHTML = '';
|
||||||
this.addColumnField(); // Add one default column
|
this.addColumnField(); // Add one default column
|
||||||
@@ -1026,12 +1091,15 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
}
|
}
|
||||||
|
|
||||||
createTable() {
|
createTable() {
|
||||||
const name = document.getElementById('newTableName').value;
|
const folder = document.getElementById('newTableFolder').value.trim();
|
||||||
|
const name = document.getElementById('newTableName').value.trim();
|
||||||
if (!name) {
|
if (!name) {
|
||||||
this.showToast('Введите название таблицы', 'error');
|
this.showToast('Введите название таблицы', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tableName = folder ? `${folder}__${name}` : name;
|
||||||
|
|
||||||
const columnElements = document.querySelectorAll('#columnsContainer > div');
|
const columnElements = document.querySelectorAll('#columnsContainer > div');
|
||||||
const columns = Array.from(columnElements).map(div => {
|
const columns = Array.from(columnElements).map(div => {
|
||||||
const inputs = div.querySelectorAll('input, select');
|
const inputs = div.querySelectorAll('input, select');
|
||||||
@@ -1051,7 +1119,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
|||||||
fetch('/api/tables', {
|
fetch('/api/tables', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ name, columns })
|
body: JSON.stringify({ name: tableName, columns })
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|||||||
158
server.js
158
server.js
@@ -4,6 +4,44 @@ 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');
|
||||||
|
|
||||||
|
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();
|
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) => {
|
app.post('/api/login', async (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
// Check against .env credentials
|
// Try users.json first
|
||||||
if (username === process.env.ADMIN_USERNAME && password === process.env.ADMIN_PASSWORD) {
|
const user = getUser(username);
|
||||||
// Test database connection
|
if (user && password === user.password) {
|
||||||
try {
|
try {
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
const result = await client.query('SELECT NOW() as time');
|
const result = await client.query('SELECT NOW() as time');
|
||||||
client.release();
|
client.release();
|
||||||
|
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
req.session.username = username;
|
req.session.username = username;
|
||||||
|
req.session.role = user.role;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Login successful',
|
message: 'Login successful',
|
||||||
|
role: user.role,
|
||||||
dbInfo: {
|
dbInfo: {
|
||||||
host: process.env.DB_HOST,
|
host: process.env.DB_HOST,
|
||||||
port: process.env.DB_PORT,
|
port: process.env.DB_PORT,
|
||||||
@@ -94,19 +134,55 @@ app.post('/api/login', async (req, res) => {
|
|||||||
serverTime: result.rows[0].time
|
serverTime: result.rows[0].time
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Database connection failed',
|
error: 'Database connection failed',
|
||||||
details: err.message
|
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
|
// Logout
|
||||||
@@ -121,6 +197,8 @@ app.get('/api/session', (req, res) => {
|
|||||||
res.json({
|
res.json({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
username: req.session.username,
|
username: req.session.username,
|
||||||
|
role: req.session.role || 'viewer',
|
||||||
|
permissions: getRolePermissions(req.session.role),
|
||||||
dbInfo: {
|
dbInfo: {
|
||||||
host: process.env.DB_HOST,
|
host: process.env.DB_HOST,
|
||||||
port: process.env.DB_PORT,
|
port: process.env.DB_PORT,
|
||||||
@@ -135,6 +213,7 @@ app.get('/api/session', (req, res) => {
|
|||||||
// Get all tables
|
// Get all tables
|
||||||
app.get('/api/tables', requireAuth, async (req, res) => {
|
app.get('/api/tables', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const role = req.session.role || 'viewer';
|
||||||
const result = await pool.query(`
|
const result = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
table_name as name,
|
table_name as name,
|
||||||
@@ -143,10 +222,13 @@ app.get('/api/tables', requireAuth, async (req, res) => {
|
|||||||
WHERE table_schema = 'public'
|
WHERE table_schema = 'public'
|
||||||
ORDER BY table_name
|
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
|
// Get row counts for each table
|
||||||
const tablesWithCounts = await Promise.all(
|
const tablesWithCounts = await Promise.all(
|
||||||
result.rows.map(async (table) => {
|
accessibleTables.map(async (table) => {
|
||||||
try {
|
try {
|
||||||
const countResult = await pool.query(`SELECT COUNT(*) as count FROM "${table.name}"`);
|
const countResult = await pool.query(`SELECT COUNT(*) as count FROM "${table.name}"`);
|
||||||
return {
|
return {
|
||||||
@@ -169,6 +251,13 @@ app.get('/api/tables', requireAuth, async (req, res) => {
|
|||||||
// Get table data with pagination, search, filters and sort
|
// Get table data with pagination, search, filters and sort
|
||||||
app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
|
app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
|
||||||
const { tableName } = req.params;
|
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 page = parseInt(req.query.page) || 1;
|
||||||
const limit = parseInt(req.query.limit) || 10;
|
const limit = parseInt(req.query.limit) || 10;
|
||||||
const search = req.query.search || '';
|
const search = req.query.search || '';
|
||||||
@@ -279,7 +368,14 @@ app.get('/api/tables/:tableName/structure', requireAuth, async (req, res) => {
|
|||||||
// Create table
|
// Create table
|
||||||
app.post('/api/tables', requireAuth, async (req, res) => {
|
app.post('/api/tables', requireAuth, async (req, res) => {
|
||||||
const { name, columns } = req.body;
|
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 {
|
try {
|
||||||
let columnsSQL = columns.map(col => {
|
let columnsSQL = columns.map(col => {
|
||||||
let def = `"${col.name}" ${col.type}`;
|
let def = `"${col.name}" ${col.type}`;
|
||||||
@@ -300,6 +396,13 @@ app.post('/api/tables', requireAuth, async (req, res) => {
|
|||||||
// Delete table
|
// Delete table
|
||||||
app.delete('/api/tables/:tableName', requireAuth, async (req, res) => {
|
app.delete('/api/tables/:tableName', requireAuth, async (req, res) => {
|
||||||
const { tableName } = req.params;
|
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 {
|
try {
|
||||||
await pool.query(`DROP TABLE IF EXISTS "${tableName}"`);
|
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) => {
|
app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => {
|
||||||
const { tableName } = req.params;
|
const { tableName } = req.params;
|
||||||
const data = req.body;
|
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 {
|
try {
|
||||||
// Get table structure to check types
|
// Get table structure to check types
|
||||||
const structureResult = await pool.query(`
|
const structureResult = await pool.query(`
|
||||||
@@ -362,8 +472,14 @@ app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => {
|
|||||||
// Update record
|
// Update record
|
||||||
app.put('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
app.put('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
||||||
const { tableName, pk } = req.params;
|
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 data = req.body;
|
||||||
|
|
||||||
const columns = Object.keys(data);
|
const columns = Object.keys(data);
|
||||||
const values = Object.values(data);
|
const values = Object.values(data);
|
||||||
const setClause = columns.map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
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
|
// Delete record
|
||||||
app.delete('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
app.delete('/api/tables/:tableName/records/:pk', requireAuth, async (req, res) => {
|
||||||
const { tableName, pk } = req.params;
|
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 {
|
try {
|
||||||
const primaryKey = await getPrimaryKeyColumn(tableName) || 'id';
|
const primaryKey = await getPrimaryKeyColumn(tableName) || 'id';
|
||||||
await pool.query(`DELETE FROM "${tableName}" WHERE "${primaryKey}" = $1`, [pk]);
|
await pool.query(`DELETE FROM "${tableName}" WHERE "${primaryKey}" = $1`, [pk]);
|
||||||
|
|||||||
34
users.json
Normal file
34
users.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"username": "superadmin",
|
||||||
|
"password": "superadmin",
|
||||||
|
"role": "superadmin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "frontend_admin",
|
||||||
|
"password": "frontend",
|
||||||
|
"role": "frontend_admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "backend_admin",
|
||||||
|
"password": "backend",
|
||||||
|
"role": "backend_admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "frontend_moder",
|
||||||
|
"password": "mod123",
|
||||||
|
"role": "frontend_moder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "backend_moder",
|
||||||
|
"password": "mod123",
|
||||||
|
"role": "backend_moder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "viewer",
|
||||||
|
"password": "viewer",
|
||||||
|
"role": "viewer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user