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

127
src/routes/admin.js Normal file
View File

@@ -0,0 +1,127 @@
const express = require('express');
module.exports = (pool) => {
const router = express.Router();
// Middleware
const requireAuth = (req, res, next) => {
if (req.session && req.session.userId) {
next();
} else {
res.status(401).json({ success: false, message: 'Unauthorized' });
}
};
const requireAdmin = (req, res, next) => {
if (req.session && (req.session.role === 'superadmin' || req.session.role === 'admin')) {
next();
} else {
res.status(403).json({ success: false, message: 'Forbidden' });
}
};
// Get system stats
router.get('/stats', requireAuth, requireAdmin, async (req, res) => {
try {
const users = await pool.query('SELECT COUNT(*) as count FROM users');
const logs = await pool.query('SELECT COUNT(*) as count FROM activity_logs');
res.json({
success: true,
stats: {
totalUsers: parseInt(users.rows[0].count),
totalLogs: parseInt(logs.rows[0].count),
activeUsers: parseInt(users.rows[0].count) // Can be improved
}
});
} catch (error) {
console.error('Get stats error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении статистики'
});
}
});
// Get activity logs
router.get('/logs', requireAuth, requireAdmin, async (req, res) => {
try {
const limit = parseInt(req.query.limit) || 100;
const result = await pool.query(
`SELECT
l.id,
l.user_id,
u.name as user_name,
l.action,
l.details,
l.created_at
FROM activity_logs l
LEFT JOIN users u ON l.user_id = u.id
ORDER BY l.created_at DESC
LIMIT $1`,
[limit]
);
res.json({
success: true,
logs: result.rows
});
} catch (error) {
console.error('Get logs error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении логов'
});
}
});
// Get backups
router.get('/backups', requireAuth, requireAdmin, async (req, res) => {
try {
// TODO: Implement actual backup system
res.json({
success: true,
backups: [
{
id: 1,
name: 'Backup 2024-01-15',
date: '2024-01-15T12:00:00Z',
size: '450 MB'
}
]
});
} catch (error) {
console.error('Get backups error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении резервных копий'
});
}
});
// Create backup
router.post('/backups', requireAuth, requireAdmin, async (req, res) => {
try {
// TODO: Implement actual backup system
res.json({
success: true,
message: 'Резервная копия создается',
backup: {
id: 2,
name: 'Backup ' + new Date().toISOString().split('T')[0],
date: new Date().toISOString(),
status: 'creating'
}
});
} catch (error) {
console.error('Create backup error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при создании резервной копии'
});
}
});
return router;
};

197
src/routes/auth.js Normal file
View File

@@ -0,0 +1,197 @@
const express = require('express');
const bcrypt = require('bcryptjs');
module.exports = (pool) => {
const router = express.Router();
// Middleware to check authentication
const requireAuth = (req, res, next) => {
if (req.session && req.session.userId) {
next();
} else {
res.status(401).json({ success: false, message: 'Unauthorized' });
}
};
// Register
router.post('/register', async (req, res) => {
try {
const { name, email, password } = req.body;
if (!email || !password || !name) {
return res.status(400).json({
success: false,
message: 'Email, name и password обязательны'
});
}
// Check if user already exists
const existing = await pool.query(
'SELECT id FROM users WHERE email = $1',
[email]
);
if (existing.rows.length > 0) {
return res.status(400).json({
success: false,
message: 'Пользователь с таким email уже существует'
});
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const result = await pool.query(
'INSERT INTO users (name, email, password, role, active) VALUES ($1, $2, $3, $4, $5) RETURNING id, name, email, role',
[name, email, hashedPassword, 'viewer', true]
);
res.json({
success: true,
message: 'Пользователь создан. Вы можете войти.',
user: result.rows[0]
});
} catch (error) {
console.error('Register error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при создании пользователя'
});
}
});
// Login
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
success: false,
message: 'Email и password обязательны'
});
}
// Find user
const result = await pool.query(
'SELECT id, name, email, password, role, active FROM users WHERE email = $1',
[email]
);
if (result.rows.length === 0) {
return res.status(401).json({
success: false,
message: 'Неверный email или пароль'
});
}
const user = result.rows[0];
if (!user.active) {
return res.status(401).json({
success: false,
message: 'Пользователь неактивен'
});
}
// Check password
const passwordValid = await bcrypt.compare(password, user.password);
if (!passwordValid) {
return res.status(401).json({
success: false,
message: 'Неверный email или пароль'
});
}
// Set session
req.session.userId = user.id;
req.session.role = user.role;
// Log activity
await pool.query(
'INSERT INTO activity_logs (user_id, action) VALUES ($1, $2)',
[user.id, 'login']
);
res.json({
success: true,
message: 'Успешно вошли',
user: {
id: user.id,
name: user.name,
email: user.email,
role: user.role
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при входе'
});
}
});
// Get current user
router.get('/me', requireAuth, async (req, res) => {
try {
const result = await pool.query(
'SELECT id, name, email, role FROM users WHERE id = $1',
[req.session.userId]
);
if (result.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Пользователь не найден'
});
}
res.json({
success: true,
user: result.rows[0]
});
} catch (error) {
console.error('Get me error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении данных пользователя'
});
}
});
// Logout
router.post('/logout', requireAuth, async (req, res) => {
try {
// Log activity
await pool.query(
'INSERT INTO activity_logs (user_id, action) VALUES ($1, $2)',
[req.session.userId, 'logout']
);
req.session.destroy((err) => {
if (err) {
return res.status(500).json({
success: false,
message: 'Ошибка при выходе'
});
}
res.json({
success: true,
message: 'Успешно вышли'
});
});
} catch (error) {
console.error('Logout error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при выходе'
});
}
});
return router;
};

174
src/routes/db-tables.js Normal file
View File

@@ -0,0 +1,174 @@
const express = require('express');
module.exports = (pool) => {
const router = express.Router();
// Middleware
const requireAuth = (req, res, next) => {
if (req.session && req.session.userId) {
next();
} else {
res.status(401).json({ success: false, message: 'Unauthorized' });
}
};
// Get all tables
router.get('/tables', requireAuth, async (req, res) => {
try {
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
`);
// Get row counts
const tablesWithCount = await Promise.all(
result.rows.map(async (table) => {
try {
const countResult = await pool.query(
`SELECT COUNT(*) as count FROM "${table.name}"`
);
return {
...table,
recordCount: parseInt(countResult.rows[0].count)
};
} catch (e) {
return {
...table,
recordCount: 0
};
}
})
);
res.json({
success: true,
tables: tablesWithCount
});
} catch (error) {
console.error('Get tables error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении таблиц'
});
}
});
// Get database stats
router.get('/stats', requireAuth, async (req, res) => {
try {
const tables = await pool.query(`
SELECT COUNT(*) as count FROM information_schema.tables
WHERE table_schema = 'public'
`);
const users = await pool.query('SELECT COUNT(*) as count FROM users');
let totalRecords = 0;
const tableNames = await pool.query(`
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
`);
for (const table of tableNames.rows) {
try {
const result = await pool.query(
`SELECT COUNT(*) as count FROM "${table.table_name}"`
);
totalRecords += parseInt(result.rows[0].count);
} catch (e) {
// Skip tables that can't be counted
}
}
res.json({
success: true,
stats: {
tableCount: parseInt(tables.rows[0].count),
recordCount: totalRecords,
userCount: parseInt(users.rows[0].count),
dbSize: '-'
}
});
} catch (error) {
console.error('Get stats error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении статистики'
});
}
});
// Get table data
router.get('/tables/:tableName/data', requireAuth, async (req, res) => {
try {
const { tableName } = req.params;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 100;
const offset = (page - 1) * limit;
const result = await pool.query(
`SELECT * FROM "${tableName}" LIMIT $1 OFFSET $2`,
[limit, offset]
);
const countResult = await pool.query(
`SELECT COUNT(*) as count FROM "${tableName}"`
);
res.json({
success: true,
data: result.rows,
total: parseInt(countResult.rows[0].count),
page,
limit
});
} catch (error) {
console.error('Get table data error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении данных таблицы'
});
}
});
// Execute query
router.post('/query', requireAuth, async (req, res) => {
try {
const { sql } = req.body;
if (!sql) {
return res.status(400).json({
success: false,
message: 'SQL запрос обязателен'
});
}
// Log query
await pool.query(
'INSERT INTO activity_logs (user_id, action, details) VALUES ($1, $2, $3)',
[req.session.userId, 'execute_query', JSON.stringify({ sql })]
);
const result = await pool.query(sql);
res.json({
success: true,
data: result.rows,
rowCount: result.rowCount
});
} catch (error) {
console.error('Execute query error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при выполнении запроса',
error: error.message
});
}
});
return router;
};

233
src/routes/users.js Normal file
View File

@@ -0,0 +1,233 @@
const express = require('express');
const bcrypt = require('bcryptjs');
module.exports = (pool) => {
const router = express.Router();
// Middleware to check authentication
const requireAuth = (req, res, next) => {
if (req.session && req.session.userId) {
next();
} else {
res.status(401).json({ success: false, message: 'Unauthorized' });
}
};
// Middleware to check admin role
const requireAdmin = (req, res, next) => {
if (req.session && req.session.role === 'superadmin' || req.session.role === 'admin') {
next();
} else {
res.status(403).json({ success: false, message: 'Forbidden' });
}
};
// Get all users (admin only)
router.get('/', requireAuth, requireAdmin, async (req, res) => {
try {
const result = await pool.query(
'SELECT id, name, email, role, active, created_at FROM users ORDER BY created_at DESC'
);
res.json({
success: true,
users: result.rows
});
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при получении пользователей'
});
}
});
// Create user (admin only)
router.post('/', requireAuth, requireAdmin, async (req, res) => {
try {
const { name, email, password, role } = req.body;
if (!email || !password || !name) {
return res.status(400).json({
success: false,
message: 'Name, email и password обязательны'
});
}
// Check if user exists
const existing = await pool.query(
'SELECT id FROM users WHERE email = $1',
[email]
);
if (existing.rows.length > 0) {
return res.status(400).json({
success: false,
message: 'Пользователь с таким email уже существует'
});
}
const hashedPassword = await bcrypt.hash(password, 10);
const result = await pool.query(
'INSERT INTO users (name, email, password, role, active) VALUES ($1, $2, $3, $4, $5) RETURNING id, name, email, role, active',
[name, email, hashedPassword, role || 'viewer', true]
);
// Log activity
await pool.query(
'INSERT INTO activity_logs (user_id, action, details) VALUES ($1, $2, $3)',
[req.session.userId, 'create_user', JSON.stringify({ userId: result.rows[0].id })]
);
res.json({
success: true,
message: 'Пользователь создан',
user: result.rows[0]
});
} catch (error) {
console.error('Create user error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при создании пользователя'
});
}
});
// Update user (admin only or self)
router.patch('/:userId', requireAuth, async (req, res) => {
try {
const { userId } = req.params;
const { name, email, password, role, active } = req.body;
// Check permission
if (req.session.userId !== parseInt(userId) && req.session.role !== 'superadmin' && req.session.role !== 'admin') {
return res.status(403).json({
success: false,
message: 'Вы не можете редактировать этого пользователя'
});
}
let query = 'UPDATE users SET ';
const updates = [];
const values = [];
let paramIndex = 1;
if (name) {
updates.push(`name = $${paramIndex}`);
values.push(name);
paramIndex++;
}
if (email) {
updates.push(`email = $${paramIndex}`);
values.push(email);
paramIndex++;
}
if (password) {
const hashedPassword = await bcrypt.hash(password, 10);
updates.push(`password = $${paramIndex}`);
values.push(hashedPassword);
paramIndex++;
}
if (role && (req.session.role === 'superadmin' || req.session.role === 'admin')) {
updates.push(`role = $${paramIndex}`);
values.push(role);
paramIndex++;
}
if (typeof active === 'boolean' && (req.session.role === 'superadmin' || req.session.role === 'admin')) {
updates.push(`active = $${paramIndex}`);
values.push(active);
paramIndex++;
}
if (updates.length === 0) {
return res.status(400).json({
success: false,
message: 'Нет данных для обновления'
});
}
updates.push(`updated_at = NOW()`);
values.push(userId);
const result = await pool.query(
`${query}${updates.join(', ')} WHERE id = $${paramIndex} RETURNING id, name, email, role, active`,
values
);
if (result.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Пользователь не найден'
});
}
// Log activity
await pool.query(
'INSERT INTO activity_logs (user_id, action, details) VALUES ($1, $2, $3)',
[req.session.userId, 'update_user', JSON.stringify({ userId, updatedFields: updates })]
);
res.json({
success: true,
message: 'Пользователь обновлен',
user: result.rows[0]
});
} catch (error) {
console.error('Update user error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при обновлении пользователя'
});
}
});
// Delete user (admin only)
router.delete('/:userId', requireAuth, requireAdmin, async (req, res) => {
try {
const { userId } = req.params;
if (parseInt(userId) === req.session.userId) {
return res.status(400).json({
success: false,
message: 'Вы не можете удалить самого себя'
});
}
const result = await pool.query(
'DELETE FROM users WHERE id = $1 RETURNING id',
[userId]
);
if (result.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Пользователь не найден'
});
}
// Log activity
await pool.query(
'INSERT INTO activity_logs (user_id, action, details) VALUES ($1, $2, $3)',
[req.session.userId, 'delete_user', JSON.stringify({ userId })]
);
res.json({
success: true,
message: 'Пользователь удален'
});
} catch (error) {
console.error('Delete user error:', error);
res.status(500).json({
success: false,
message: 'Ошибка при удалении пользователя'
});
}
});
return router;
};