init
This commit is contained in:
127
src/routes/admin.js
Normal file
127
src/routes/admin.js
Normal 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
197
src/routes/auth.js
Normal 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
174
src/routes/db-tables.js
Normal 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
233
src/routes/users.js
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user