/
varsl
/
HTML-CSS
Обзор
Документация
Войти
/
varsl
/
HTML-CSS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
backend/routes/auth.js
788 строк
31 KB
varsl
Создание маршрутов для аутентификации
21 дек 2025, 23:53
21 дек 2025, 23:53
f569b68
Код
Авторство
О чём код?
const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const { jwtSecret } = require('../config'); const User = require('../models/User'); const { authMiddleware } = require('../middleware/authMiddleware'); const { body, validationResult } = require('express-validator'); const sqlite3 = require('sqlite3').verbose(); const path = require('path'); const { dbPath } = require('../config'); // Валидация регистрации const validateRegistration = [ body('email') .isEmail() .withMessage('Введите корректный email адрес'), body('password') .isLength({ min: 6 }) .withMessage('Пароль должен содержать не менее 6 символов'), body('name') .trim() .notEmpty() .withMessage('Имя обязательно для заполнения') .isLength({ min: 2, max: 50 }) .withMessage('Имя должно содержать от 2 до 50 символов') ]; // Валидация входа const validateLogin = [ body('email') .isEmail() .withMessage('Введите корректный email адрес'), body('password') .notEmpty() .withMessage('Пароль обязателен для заполнения') ]; // Регистрация пользователя router.post('/register', validateRegistration, async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { console.log('Ошибки валидации:', errors.array()); return res.status(400).json({ success: false, message: 'Ошибка валидации', errors: errors.array().map(err => ({ field: err.path, message: err.msg })) }); } const { email, password, name, phone, birthDate, newsletter = false } = req.body; console.log('Регистрация:', { email, name }); const existingUser = await User.findByEmail(email); if (existingUser) { return res.status(400).json({ success: false, message: 'Пользователь с таким email уже существует' }); } const userId = await User.create({ email, password, name, phone: phone || null, birthDate: birthDate || null, newsletter }); const token = jwt.sign({ userId }, jwtSecret, { expiresIn: '7d' }); const user = await User.findById(userId); console.log('Пользователь зарегистрирован:', user.email); res.status(201).json({ success: true, message: 'Регистрация успешно завершена', user: { id: user.id, email: user.email, name: user.name, phone: user.phone, birthDate: user.birth_date, newsletter: user.newsletter, role: user.role, createdAt: user.created_at }, token }); } catch (error) { console.error('Register error:', error); res.status(500).json({ success: false, message: 'Ошибка при регистрации' }); } }); // Вход пользователя router.post('/login', validateLogin, async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Ошибка валидации', errors: errors.array() }); } const { email, password } = req.body; const user = await User.findByEmail(email); if (!user) { return res.status(401).json({ success: false, message: 'Неверный email или пароль' }); } const isPasswordValid = await User.verifyPassword(password, user.password); if (!isPasswordValid) { return res.status(401).json({ success: false, message: 'Неверный email или пароль' }); } const token = jwt.sign({ userId: user.id }, jwtSecret, { expiresIn: '7d' }); res.json({ success: true, message: 'Вход выполнен успешно', user: { id: user.id, email: user.email, name: user.name, phone: user.phone, birthDate: user.birth_date, newsletter: user.newsletter, role: user.role, createdAt: user.created_at }, token }); } catch (error) { console.error('Login error:', error); res.status(500).json({ success: false, message: 'Ошибка при входе' }); } }); // Получение текущего пользователя router.get('/me', authMiddleware, async (req, res) => { try { res.json({ success: true, user: req.user }); } catch (error) { console.error('Get user error:', error); res.status(500).json({ success: false, message: 'Ошибка при получении данных пользователя' }); } }); // Получение данных дашборда router.get('/dashboard-data', authMiddleware, async (req, res) => { try { const userId = req.user.id; // Создаем новое подключение к базе данных const db = new sqlite3.Database(path.resolve(dbPath), (err) => { if (err) { console.error('Error opening database:', err); return res.status(500).json({ success: false, message: 'Ошибка подключения к базе данных' }); } }); // Функция для получения статистики const getStats = () => { return new Promise((resolve, reject) => { const stats = {}; // Количество активных билетов db.get( 'SELECT COUNT(*) as count FROM tickets WHERE user_id = ? AND status = ?', [userId, 'active'], (err, row) => { if (err) { console.error('Error getting tickets:', err); stats.tickets = 0; } else { stats.tickets = row.count; } // Количество посещений db.get( 'SELECT COUNT(*) as count FROM visits WHERE user_id = ?', [userId], (err, row) => { if (err) { console.error('Error getting visits:', err); stats.visits = 0; } else { stats.visits = row.count; } // Количество любимых животных db.get( 'SELECT COUNT(*) as count FROM favorites WHERE user_id = ?', [userId], (err, row) => { if (err) { console.error('Error getting favorites:', err); stats.favorites = 0; } else { stats.favorites = row.count; } // Количество отзывов db.get( 'SELECT COUNT(*) as count FROM reviews WHERE user_id = ?', [userId], (err, row) => { if (err) { console.error('Error getting reviews:', err); stats.reviews = 0; } else { stats.reviews = row.count; } resolve(stats); } ); } ); } ); } ); }); }; // Функция для получения данных графиков const getChartsData = () => { return new Promise((resolve, reject) => { const charts = {}; // Данные для графика посещений по месяцам db.all( `SELECT strftime('%m', created_at) as month, COUNT(*) as count FROM visits WHERE user_id = ? GROUP BY strftime('%m', created_at) ORDER BY month`, [userId], (err, rows) => { if (err) { console.error('Error getting visits chart data:', err); charts.visits = Array(12).fill(0); } else { const visitsData = Array(12).fill(0); rows.forEach(row => { const monthIndex = parseInt(row.month) - 1; visitsData[monthIndex] = row.count; }); charts.visits = visitsData; } // Данные для графика популярности животных db.all( `SELECT a.name as animal_name, COUNT(f.id) as favorites_count FROM favorites f JOIN animals a ON f.animal_id = a.animal_id WHERE f.user_id = ? GROUP BY a.name ORDER BY favorites_count DESC LIMIT 6`, [userId], (err, rows) => { if (err) { console.error('Error getting animal popularity data:', err); charts.animalPopularity = [25, 20, 15, 15, 12, 13]; charts.animalLabels = ['Львы', 'Слоны', 'Панды', 'Тигры', 'Жирафы', 'Пингвины']; } else { if (rows.length > 0) { charts.animalPopularity = rows.map(row => row.favorites_count); charts.animalLabels = rows.map(row => row.animal_name); } else { // Тестовые данные, если нет избранных charts.animalPopularity = [25, 20, 15, 15, 12, 13]; charts.animalLabels = ['Львы', 'Слоны', 'Панды', 'Тигры', 'Жирафы', 'Пингвины']; } } // Данные для графика продаж билетов по месяцам db.all( `SELECT strftime('%m', created_at) as month, SUM(total_amount) as revenue FROM tickets WHERE user_id = ? GROUP BY strftime('%m', created_at) ORDER BY month`, [userId], (err, rows) => { if (err) { console.error('Error getting revenue data:', err); charts.revenue = Array(12).fill(0); } else { const revenueData = Array(12).fill(0); rows.forEach(row => { const monthIndex = parseInt(row.month) - 1; revenueData[monthIndex] = row.revenue || 0; }); charts.revenue = revenueData; } resolve(charts); } ); } ); } ); }); }; // Функция для получения недавней активности const getRecentActivity = () => { return new Promise((resolve, reject) => { db.all( `SELECT 'ticket' as type, 'Куплен билет: ' || customer_name as title, created_at as timestamp FROM tickets WHERE user_id = ? UNION ALL SELECT 'visit' as type, 'Посещение зоопарка' as title, created_at as timestamp FROM visits WHERE user_id = ? UNION ALL SELECT 'review' as type, 'Оставлен отзыв о животном' as title, created_at as timestamp FROM reviews WHERE user_id = ? UNION ALL SELECT 'favorite' as type, 'Добавлено в избранное' as title, created_at as timestamp FROM favorites WHERE user_id = ? ORDER BY timestamp DESC LIMIT 5`, [userId, userId, userId, userId], (err, rows) => { if (err) { console.error('Error getting recent activity:', err); // Возвращаем тестовые данные resolve([ { type: 'ticket', title: 'Куплен билет на 2 взрослых', timestamp: new Date(Date.now() - 7200000).toISOString() }, { type: 'favorite', title: 'Добавлено в избранное: Лев Симба', timestamp: new Date(Date.now() - 86400000).toISOString() }, { type: 'review', title: 'Оставлен отзыв о посещении', timestamp: new Date(Date.now() - 259200000).toISOString() } ]); } else { // Если нет данных, возвращаем тестовые if (rows.length === 0) { resolve([ { type: 'ticket', title: 'Куплен билет на 2 взрослых', timestamp: new Date(Date.now() - 7200000).toISOString() }, { type: 'favorite', title: 'Добавлено в избранное: Лев Симба', timestamp: new Date(Date.now() - 86400000).toISOString() }, { type: 'review', title: 'Оставлен отзыв о посещении', timestamp: new Date(Date.now() - 259200000).toISOString() } ]); } else { resolve(rows); } } } ); }); }; // Функция для получения достижений const getAchievements = () => { return new Promise((resolve, reject) => { const achievements = []; // Проверяем различные достижения const promises = [ new Promise((res) => { db.get('SELECT COUNT(*) as count FROM tickets WHERE user_id = ?', [userId], (err, row) => { if (!err) { if (row.count >= 1) achievements.push({ title: 'Первый билет', description: 'Купил первый билет' }); if (row.count >= 5) achievements.push({ title: 'Частый посетитель', description: 'Купил 5 билетов' }); if (row.count >= 10) achievements.push({ title: 'ВИП клиент', description: 'Купил 10 билетов' }); } res(); }); }), new Promise((res) => { db.get('SELECT COUNT(*) as count FROM reviews WHERE user_id = ?', [userId], (err, row) => { if (!err) { if (row.count >= 1) achievements.push({ title: 'Первый отзыв', description: 'Оставил первый отзыв' }); if (row.count >= 5) achievements.push({ title: 'Критик', description: 'Оставил 5 отзывов' }); } res(); }); }), new Promise((res) => { db.get('SELECT COUNT(*) as count FROM favorites WHERE user_id = ?', [userId], (err, row) => { if (!err) { if (row.count >= 1) achievements.push({ title: 'Любитель животных', description: 'Добавил животное в избранное' }); if (row.count >= 3) achievements.push({ title: 'Натуралист', description: 'Добавил 3 животных в избранное' }); } res(); }); }), new Promise((res) => { db.get('SELECT COUNT(*) as count FROM visits WHERE user_id = ?', [userId], (err, row) => { if (!err) { if (row.count >= 1) achievements.push({ title: 'Первый визит', description: 'Первое посещение зоопарка' }); if (row.count >= 3) achievements.push({ title: 'Завсегдатай', description: 'Посетил зоопарк 3 раза' }); } res(); }); }) ]; Promise.all(promises).then(() => { resolve(achievements); }).catch(() => { resolve([]); }); }); }; try { const [stats, charts, recentActivity, achievements] = await Promise.all([ getStats(), getChartsData(), getRecentActivity(), getAchievements() ]); // Закрываем соединение с базой данных db.close(); res.json({ success: true, stats, charts, recentActivity, achievements, user: req.user }); } catch (error) { console.error('Dashboard data error:', error); if (db) { db.close(); } res.status(500).json({ success: false, message: 'Ошибка при получении данных дашборда' }); } } catch (error) { console.error('Dashboard error:', error); res.status(500).json({ success: false, message: 'Ошибка сервера' }); } }); // Обновление профиля router.put('/profile', [ authMiddleware, body('name') .optional() .trim() .isLength({ min: 2, max: 50 }) .withMessage('Имя должно содержать от 2 до 50 символов') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Ошибка валидации', errors: errors.array() }); } const { name, currentPassword, newPassword } = req.body; const updates = {}; if (name !== undefined) { updates.name = name; } if (currentPassword && newPassword) { const userWithPassword = await User.findByEmail(req.user.email); const isCurrentPasswordValid = await User.verifyPassword( currentPassword, userWithPassword.password ); if (!isCurrentPasswordValid) { return res.status(400).json({ success: false, message: 'Текущий пароль неверен' }); } const hashedNewPassword = await bcrypt.hash(newPassword, 10); updates.password = hashedNewPassword; } if (Object.keys(updates).length > 0) { const updated = await User.update(req.user.id, updates); if (!updated) { return res.status(400).json({ success: false, message: 'Не удалось обновить профиль' }); } } const updatedUser = await User.findById(req.user.id); res.json({ success: true, message: 'Профиль успешно обновлен', user: updatedUser }); } catch (error) { console.error('Update profile error:', error); res.status(500).json({ success: false, message: 'Ошибка при обновлении профиля' }); } }); // Смена пароля router.post('/change-password', [ authMiddleware, body('currentPassword') .notEmpty() .withMessage('Текущий пароль обязателен'), body('newPassword') .isLength({ min: 6 }) .withMessage('Новый пароль должен содержать не менее 6 символов') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Ошибка валидации', errors: errors.array() }); } const { currentPassword, newPassword } = req.body; const user = await User.findByEmail(req.user.email); const isCurrentPasswordValid = await User.verifyPassword( currentPassword, user.password ); if (!isCurrentPasswordValid) { return res.status(400).json({ success: false, message: 'Текущий пароль неверен' }); } const hashedNewPassword = await bcrypt.hash(newPassword, 10); const updated = await User.update(req.user.id, { password: hashedNewPassword }); if (!updated) { return res.status(400).json({ success: false, message: 'Не удалось изменить пароль' }); } res.json({ success: true, message: 'Пароль успешно изменен' }); } catch (error) { console.error('Change password error:', error); res.status(500).json({ success: false, message: 'Ошибка при смене пароля' }); } }); // Выход из системы router.post('/logout', authMiddleware, (req, res) => { try { res.json({ success: true, message: 'Выход выполнен успешно' }); } catch (error) { console.error('Logout error:', error); res.status(500).json({ success: false, message: 'Ошибка при выходе из системы' }); } }); // Проверка доступности email router.get('/check-email/:email', async (req, res) => { try { const { email } = req.params; if (!email) { return res.status(400).json({ success: false, message: 'Email обязателен' }); } const user = await User.findByEmail(email); res.json({ success: true, available: !user, message: user ? 'Email уже используется' : 'Email доступен' }); } catch (error) { console.error('Check email error:', error); res.status(500).json({ success: false, message: 'Ошибка при проверке email' }); } }); // Обновление токена router.post('/refresh-token', authMiddleware, async (req, res) => { try { const newToken = jwt.sign({ userId: req.user.id }, jwtSecret, { expiresIn: '7d' }); res.json({ success: true, token: newToken, user: req.user }); } catch (error) { console.error('Refresh token error:', error); res.status(500).json({ success: false, message: 'Ошибка при обновлении токена' }); } }); // Удаление аккаунта router.delete('/account', authMiddleware, async (req, res) => { try { const { password } = req.body; if (!password) { return res.status(400).json({ success: false, message: 'Пароль обязателен для удаления аккаунта' }); } const user = await User.findByEmail(req.user.email); const isPasswordValid = await User.verifyPassword(password, user.password); if (!isPasswordValid) { return res.status(400).json({ success: false, message: 'Неверный пароль' }); } const deleted = await User.delete(req.user.id); if (!deleted) { return res.status(400).json({ success: false, message: 'Не удалось удалить аккаунт' }); } res.json({ success: true, message: 'Аккаунт успешно удален' }); } catch (error) { console.error('Delete account error:', error); res.status(500).json({ success: false, message: 'Ошибка при удалении аккаунта' }); } }); module.exports = router;