205 lines
6.3 KiB
JavaScript
205 lines
6.3 KiB
JavaScript
const db = require('../config/db');
|
|
const {sendVerificationEmail, sendResetEmail} = require('../utils/sendEmail');
|
|
const bcrypt = require('bcrypt');
|
|
const jwt = require('jsonwebtoken');
|
|
const nodemailer = require('nodemailer');
|
|
const crypto = require('crypto');
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
service: 'gmail',
|
|
auth: {
|
|
user: process.env.EMAIL_USER,
|
|
pass: process.env.EMAIL_PASS
|
|
}
|
|
});
|
|
|
|
exports.registerUser = async (req, res) => {
|
|
try {
|
|
const {username, email, password} = req.body;
|
|
|
|
// Verifica si ya existe un usuario con ese username o email
|
|
const [existing] = await db.execute(
|
|
'SELECT * FROM users WHERE username = ? OR email = ?',
|
|
[username, email]
|
|
);
|
|
|
|
if (existing.length > 0) {
|
|
return res.status(400).json({success: false, message: "Usuario ya registrado."});
|
|
}
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
|
|
// Inserta el nuevo usuario con el token
|
|
await db.execute(
|
|
'INSERT INTO users (username, email, password, is_verified, verificationToken) VALUES (?, ?, ?, 0, ?)',
|
|
[username, email, hashedPassword, token]
|
|
);
|
|
|
|
await sendVerificationEmail(email, token);
|
|
|
|
res.status(201).json({success: true, message: "Usuario registrado. Verifica tu correo."});
|
|
|
|
} catch (err) {
|
|
console.error("❌ Error en registerUser:", err);
|
|
res.status(500).json({success: false, message: "Error en el servidor."});
|
|
}
|
|
};
|
|
|
|
|
|
exports.verifyEmail = async (req, res) => {
|
|
const token = req.params.token;
|
|
try {
|
|
const [rows] = await db.execute(
|
|
'SELECT * FROM users WHERE verificationToken = ?',
|
|
[token]
|
|
);
|
|
|
|
if (rows.length === 0) {
|
|
return res.redirect('/verify-failed.html');
|
|
}
|
|
|
|
await db.execute(
|
|
'UPDATE users SET is_verified = 1, verificationToken = NULL WHERE verificationToken = ?',
|
|
[token]
|
|
);
|
|
|
|
return res.redirect('/verify-success.html');
|
|
|
|
} catch (err) {
|
|
console.error("❌ Error en verifyEmail:", err);
|
|
return res.redirect('/verify-failed.html');
|
|
}
|
|
};
|
|
|
|
|
|
exports.loginUser = async (req, res) => {
|
|
const {username, password} = req.body;
|
|
if (!username || !password) {
|
|
return res.status(400).json({success: false, message: 'Usuario y contraseña requeridos'});
|
|
}
|
|
|
|
try {
|
|
const [results] = await db.execute('SELECT * FROM users WHERE username = ? LIMIT 1', [username]);
|
|
if (results.length === 0) {
|
|
return res.status(401).json({success: false, message: 'Usuario no encontrado'});
|
|
}
|
|
|
|
const user = results[0];
|
|
const passwordMatch = await bcrypt.compare(password, user.password);
|
|
|
|
if (!passwordMatch) {
|
|
return res.status(401).json({success: false, message: 'Contraseña incorrecta'});
|
|
}
|
|
|
|
if (!user.is_verified) {
|
|
return res.status(403).json({success: false, message: 'Tu correo no ha sido verificado'});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
user: {id: user.id, username: user.username},
|
|
message: 'Inicio de sesión exitoso'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({success: false, message: 'Error en el servidor', error: err.message});
|
|
}
|
|
};
|
|
|
|
exports.forgotPassword = async (req, res) => {
|
|
const {email} = req.body;
|
|
|
|
try {
|
|
const [users] = await db.execute('SELECT id FROM users WHERE email = ?', [email]);
|
|
if (users.length === 0) return res.status(404).json({message: 'Correo no encontrado'});
|
|
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
await db.execute('INSERT INTO password_resets (user_id, token) VALUES (?, ?)', [users[0].id, token]);
|
|
|
|
const resetLink = `http://localhost:3000/reset-password/${token}`;
|
|
|
|
// ✔️ Usa la función reutilizable
|
|
await sendResetEmail(email, resetLink);
|
|
|
|
res.json({message: 'Correo enviado con instrucciones'});
|
|
} catch (err) {
|
|
console.error('Error al enviar token de recuperación:', err);
|
|
res.status(500).json({message: 'Error interno del servidor'});
|
|
}
|
|
};
|
|
|
|
exports.resetPassword = async (req, res) => {
|
|
const {email, token, newPassword} = req.body;
|
|
|
|
if (!email || !token || !newPassword) {
|
|
return res.status(400).json({message: 'Todos los campos son obligatorios.'});
|
|
}
|
|
|
|
try {
|
|
const [rows] = await db.execute(
|
|
'SELECT user_id FROM password_resets WHERE token = ?',
|
|
[token]
|
|
);
|
|
|
|
if (rows.length === 0) {
|
|
return res.status(400).json({message: 'Token inválido o expirado'});
|
|
}
|
|
|
|
const userId = rows[0].user_id;
|
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
|
|
|
await db.execute(
|
|
'UPDATE users SET password = ? WHERE id = ?',
|
|
[hashedPassword, userId]
|
|
);
|
|
|
|
await db.execute(
|
|
'DELETE FROM password_resets WHERE token = ?',
|
|
[token]
|
|
);
|
|
|
|
res.json({message: 'Contraseña actualizada correctamente'});
|
|
|
|
} catch (err) {
|
|
console.error('❌ Error al restablecer contraseña:', err);
|
|
res.status(500).json({message: 'Error interno del servidor'});
|
|
}
|
|
};
|
|
|
|
|
|
exports.getUserStats = async (req, res) => {
|
|
const userId = req.params.id;
|
|
|
|
try {
|
|
const [animeStatsRows] = await db.execute(
|
|
'SELECT status, COUNT(*) as count FROM anime WHERE userId = ? GROUP BY status',
|
|
[userId]
|
|
);
|
|
|
|
const [mangaStatsRows] = await db.execute(
|
|
'SELECT status, COUNT(*) as count FROM manga WHERE userId = ? GROUP BY status',
|
|
[userId]
|
|
);
|
|
|
|
const formatStats = (rows) => {
|
|
const stats = {};
|
|
rows.forEach(row => {
|
|
stats[row.status] = row.count;
|
|
});
|
|
return stats;
|
|
};
|
|
|
|
res.json({
|
|
animeStats: animeStatsRows.length ? formatStats(animeStatsRows) : {},
|
|
mangaStats: mangaStatsRows.length ? formatStats(mangaStatsRows) : {}
|
|
});
|
|
|
|
} catch (err) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error al obtener estadísticas',
|
|
error: err.message
|
|
});
|
|
}
|
|
};
|