497 lines
No EOL
14 KiB
JavaScript
497 lines
No EOL
14 KiB
JavaScript
const express = require('express');
|
|
const jwt = require('jsonwebtoken');
|
|
const bcrypt = require('bcryptjs');
|
|
const { body, validationResult } = require('express-validator');
|
|
const { User } = require('../models');
|
|
const { auth, requireSuperAdmin } = require('../middleware/auth');
|
|
|
|
const router = express.Router();
|
|
|
|
// Generate JWT token
|
|
const generateToken = (userId) => {
|
|
return jwt.sign({ userId }, process.env.JWT_SECRET, {
|
|
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
|
|
});
|
|
};
|
|
|
|
// @route GET /api/auth/setup-status
|
|
// @desc Check if system setup is required
|
|
// @access Public
|
|
router.get('/setup-status', async (req, res) => {
|
|
try {
|
|
const superAdminCount = await User.count({ where: { role: 'super_admin' } });
|
|
res.json({
|
|
setupRequired: superAdminCount === 0,
|
|
superAdminCount
|
|
});
|
|
} catch (error) {
|
|
console.error('Setup status check error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/auth/setup
|
|
// @desc First-time setup - Create super admin (Public)
|
|
// @access Public (only when no SA exists)
|
|
router.post('/setup', [
|
|
body('firstName').isLength({ min: 2, max: 50 }),
|
|
body('lastName').isLength({ min: 2, max: 50 }),
|
|
body('email').isEmail().normalizeEmail(),
|
|
body('password').isLength({ min: 6 }),
|
|
body('phone').notEmpty().withMessage('Phone number is required'),
|
|
body('isActive').optional().isBoolean()
|
|
], async (req, res) => {
|
|
try {
|
|
// Check if setup is already completed
|
|
const superAdminCount = await User.count({ where: { role: 'super_admin' } });
|
|
if (superAdminCount > 0) {
|
|
return res.status(400).json({ error: 'System setup already completed.' });
|
|
}
|
|
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { firstName, lastName, email, password, phone } = req.body;
|
|
|
|
// Check if user already exists (by email or phone)
|
|
const existingUser = await User.findOne({
|
|
where: {
|
|
[require('sequelize').Op.or]: [
|
|
{ email },
|
|
{ phone }
|
|
]
|
|
}
|
|
});
|
|
|
|
if (existingUser) {
|
|
if (existingUser.email === email) {
|
|
return res.status(400).json({ error: 'User with this email already exists.' });
|
|
} else {
|
|
return res.status(400).json({ error: 'User with this phone number already exists.' });
|
|
}
|
|
}
|
|
|
|
// Create the first super admin
|
|
const superAdmin = await User.create({
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
password,
|
|
role: 'super_admin',
|
|
phone,
|
|
isActive: true
|
|
});
|
|
|
|
console.log('Super Admin created during setup:', {
|
|
id: superAdmin.id,
|
|
email: superAdmin.email,
|
|
phone: superAdmin.phone,
|
|
role: superAdmin.role
|
|
});
|
|
|
|
// Generate token for immediate login
|
|
const token = generateToken(superAdmin.id);
|
|
|
|
res.status(201).json({
|
|
message: 'System setup completed successfully. Super Admin account created.',
|
|
token,
|
|
user: {
|
|
id: superAdmin.id,
|
|
firstName: superAdmin.firstName,
|
|
lastName: superAdmin.lastName,
|
|
email: superAdmin.email,
|
|
phone: superAdmin.phone,
|
|
role: superAdmin.role
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Setup error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/auth/login
|
|
// @desc Login user
|
|
// @access Public
|
|
router.post('/login', [
|
|
body('identifier').notEmpty().withMessage('Email or phone number is required'),
|
|
body('password').isLength({ min: 6 })
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { identifier, password } = req.body;
|
|
|
|
// Find user by email or phone
|
|
const user = await User.findOne({
|
|
where: {
|
|
[require('sequelize').Op.or]: [
|
|
{ email: identifier },
|
|
{ phone: identifier }
|
|
],
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
console.log('Login attempt for identifier:', identifier, 'User found:', !!user, 'User active:', user?.isActive);
|
|
|
|
if (!user || !user.isActive) {
|
|
return res.status(401).json({ error: 'Invalid credentials or account inactive.' });
|
|
}
|
|
|
|
const isPasswordValid = await user.comparePassword(password);
|
|
console.log('Password validation result:', isPasswordValid);
|
|
|
|
if (!isPasswordValid) {
|
|
return res.status(401).json({ error: 'Invalid credentials.' });
|
|
}
|
|
|
|
// Update last login
|
|
await user.update({ lastLogin: new Date() });
|
|
|
|
const token = generateToken(user.id);
|
|
|
|
res.json({
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
phone: user.phone,
|
|
role: user.role,
|
|
avatar: user.avatar,
|
|
requiresPasswordChange: user.requiresPasswordChange
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/auth/register
|
|
// @desc Register new user (Super Admin only)
|
|
// @access Private (Super Admin)
|
|
router.post('/register', [
|
|
auth,
|
|
requireSuperAdmin,
|
|
body('firstName').isLength({ min: 2, max: 50 }),
|
|
body('lastName').isLength({ min: 2, max: 50 }),
|
|
body('email').isEmail().normalizeEmail(),
|
|
body('password').isLength({ min: 6 }),
|
|
body('role').isIn(['super_admin', 'trainer', 'trainee']),
|
|
body('phone').notEmpty().withMessage('Phone number is required')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { firstName, lastName, email, password, role, phone, isActive = true } = req.body;
|
|
|
|
// Check if user already exists
|
|
const existingUser = await User.findOne({ where: { email } });
|
|
if (existingUser) {
|
|
return res.status(400).json({ error: 'User with this email already exists.' });
|
|
}
|
|
|
|
// Prevent creating another super admin if not during setup
|
|
if (role === 'super_admin') {
|
|
const superAdminCount = await User.count({ where: { role: 'super_admin' } });
|
|
if (superAdminCount > 0) {
|
|
return res.status(400).json({ error: 'Super Admin account already exists. Cannot create another one.' });
|
|
}
|
|
}
|
|
|
|
const user = await User.create({
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
password,
|
|
role,
|
|
phone,
|
|
isActive
|
|
});
|
|
|
|
console.log('User created successfully:', {
|
|
id: user.id,
|
|
email: user.email,
|
|
role: user.role,
|
|
isActive: user.isActive
|
|
});
|
|
|
|
res.status(201).json({
|
|
message: 'User created successfully.',
|
|
user: {
|
|
id: user.id,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
role: user.role,
|
|
phone: user.phone,
|
|
isActive: user.isActive
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Registration error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route GET /api/auth/me
|
|
// @desc Get current user
|
|
// @access Private
|
|
router.get('/me', auth, async (req, res) => {
|
|
try {
|
|
res.json({
|
|
user: {
|
|
id: req.user.id,
|
|
firstName: req.user.firstName,
|
|
lastName: req.user.lastName,
|
|
email: req.user.email,
|
|
role: req.user.role,
|
|
avatar: req.user.avatar,
|
|
phone: req.user.phone,
|
|
lastLogin: req.user.lastLogin
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get user error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route PUT /api/auth/profile
|
|
// @desc Update user profile
|
|
// @access Private
|
|
router.put('/profile', [
|
|
auth,
|
|
body('firstName').optional().isLength({ min: 2, max: 50 }),
|
|
body('lastName').optional().isLength({ min: 2, max: 50 }),
|
|
body('phone').optional()
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { firstName, lastName, phone } = req.body;
|
|
const updateData = {};
|
|
|
|
if (firstName) updateData.firstName = firstName;
|
|
if (lastName) updateData.lastName = lastName;
|
|
if (phone) updateData.phone = phone;
|
|
|
|
await req.user.update(updateData);
|
|
|
|
res.json({
|
|
message: 'Profile updated successfully.',
|
|
user: {
|
|
id: req.user.id,
|
|
firstName: req.user.firstName,
|
|
lastName: req.user.lastName,
|
|
email: req.user.email,
|
|
role: req.user.role,
|
|
avatar: req.user.avatar,
|
|
phone: req.user.phone
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Profile update error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route PUT /api/auth/change-password
|
|
// @desc Change user password
|
|
// @access Private
|
|
router.put('/change-password', [
|
|
auth,
|
|
body('currentPassword').isLength({ min: 6 }),
|
|
body('newPassword').isLength({ min: 6 })
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { currentPassword, newPassword } = req.body;
|
|
|
|
const isCurrentPasswordValid = await req.user.comparePassword(currentPassword);
|
|
if (!isCurrentPasswordValid) {
|
|
return res.status(400).json({ error: 'Current password is incorrect.' });
|
|
}
|
|
|
|
await req.user.update({ password: newPassword });
|
|
|
|
res.json({ message: 'Password changed successfully.' });
|
|
} catch (error) {
|
|
console.error('Password change error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route PUT /api/auth/first-password-change
|
|
// @desc Change password on first login (for imported users)
|
|
// @access Private
|
|
router.put('/first-password-change', [
|
|
auth,
|
|
body('newPassword').isLength({ min: 6 })
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { newPassword } = req.body;
|
|
|
|
// Check if user requires password change
|
|
if (!req.user.requiresPasswordChange) {
|
|
return res.status(400).json({ error: 'Password change not required.' });
|
|
}
|
|
|
|
await req.user.update({
|
|
password: newPassword,
|
|
requiresPasswordChange: false
|
|
});
|
|
|
|
res.json({
|
|
message: 'Password changed successfully.',
|
|
user: {
|
|
id: req.user.id,
|
|
firstName: req.user.firstName,
|
|
lastName: req.user.lastName,
|
|
email: req.user.email,
|
|
role: req.user.role,
|
|
avatar: req.user.avatar,
|
|
phone: req.user.phone,
|
|
requiresPasswordChange: false
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('First password change error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/auth/trainee-login
|
|
// @desc Login trainee with phone or email
|
|
// @access Public
|
|
router.post('/trainee-login', [
|
|
body('identifier').notEmpty().withMessage('Phone or email is required'),
|
|
body('password').isLength({ min: 6 })
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { identifier, password } = req.body;
|
|
|
|
// Find user by email or phone
|
|
const user = await User.findOne({
|
|
where: {
|
|
[require('sequelize').Op.or]: [
|
|
{ email: identifier },
|
|
{ phone: identifier }
|
|
],
|
|
role: 'trainee',
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(401).json({ error: 'Invalid credentials or account inactive.' });
|
|
}
|
|
|
|
const isPasswordValid = await user.comparePassword(password);
|
|
|
|
if (!isPasswordValid) {
|
|
return res.status(401).json({ error: 'Invalid credentials.' });
|
|
}
|
|
|
|
// Update last login
|
|
await user.update({ lastLogin: new Date() });
|
|
|
|
const token = generateToken(user.id);
|
|
|
|
res.json({
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
phone: user.phone,
|
|
role: user.role,
|
|
avatar: user.avatar,
|
|
requiresPasswordChange: user.requiresPasswordChange
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Trainee login error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/auth/check-enrollment
|
|
// @desc Check if trainee is enrolled in any courses
|
|
// @access Private (Trainee)
|
|
router.post('/check-enrollment', auth, async (req, res) => {
|
|
try {
|
|
if (req.user.role !== 'trainee') {
|
|
return res.status(403).json({ error: 'Only trainees can check enrollment.' });
|
|
}
|
|
|
|
const { Enrollment, Course } = require('../models');
|
|
|
|
const enrollments = await Enrollment.findAll({
|
|
where: {
|
|
userId: req.user.id,
|
|
status: ['active', 'pending']
|
|
},
|
|
include: [
|
|
{
|
|
model: Course,
|
|
as: 'course',
|
|
attributes: ['id', 'title', 'thumbnail', 'description'],
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'trainer',
|
|
attributes: ['id', 'firstName', 'lastName']
|
|
}
|
|
]
|
|
}
|
|
],
|
|
order: [['enrolledAt', 'DESC']]
|
|
});
|
|
|
|
res.json({
|
|
hasEnrollments: enrollments.length > 0,
|
|
enrollments: enrollments.map(e => ({
|
|
id: e.id,
|
|
status: e.status,
|
|
progress: e.progress,
|
|
enrolledAt: e.enrolledAt,
|
|
course: e.course
|
|
}))
|
|
});
|
|
} catch (error) {
|
|
console.error('Check enrollment error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|