- Move email templates to separate files in src/emails/templates/ - Create new email service architecture (service.js, index.js) - Add recording suggestions email template for matching notifications - Integrate email notifications with matching system (sends when suggestions created) - Update controllers (auth.js, user.js) to use new email module - Update tests to use new email module path - Remove deprecated src/utils/email.js Features: - Template-based email system for easy editing - Automatic email notifications when recording assignments are made - Clean separation between template logic and sending logic - Graceful error handling for AWS SES failures
152 lines
4.7 KiB
JavaScript
152 lines
4.7 KiB
JavaScript
const { prisma } = require('../utils/db');
|
|
const { hashPassword, comparePassword, generateToken, generateVerificationToken, generateVerificationCode } = require('../utils/auth');
|
|
const { sendVerificationEmail } = require('../emails');
|
|
const { sanitizeForEmail } = require('../utils/sanitize');
|
|
|
|
/**
|
|
* Update user profile
|
|
* PATCH /api/users/me
|
|
*/
|
|
async function updateProfile(req, res, next) {
|
|
try {
|
|
const userId = req.user.id;
|
|
const { firstName, lastName, email, wsdcId, youtubeUrl, instagramUrl, facebookUrl, tiktokUrl, country, city } = req.body;
|
|
|
|
// Build update data
|
|
const updateData = {};
|
|
if (firstName !== undefined) updateData.firstName = firstName;
|
|
if (lastName !== undefined) updateData.lastName = lastName;
|
|
if (wsdcId !== undefined) updateData.wsdcId = wsdcId || null; // Allow empty string to clear WSDC ID
|
|
if (youtubeUrl !== undefined) updateData.youtubeUrl = youtubeUrl || null;
|
|
if (instagramUrl !== undefined) updateData.instagramUrl = instagramUrl || null;
|
|
if (facebookUrl !== undefined) updateData.facebookUrl = facebookUrl || null;
|
|
if (tiktokUrl !== undefined) updateData.tiktokUrl = tiktokUrl || null;
|
|
if (country !== undefined) updateData.country = country || null;
|
|
if (city !== undefined) updateData.city = city || null;
|
|
|
|
// Check if email is being changed
|
|
const currentUser = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { email: true },
|
|
});
|
|
|
|
let emailChanged = false;
|
|
if (email && email !== currentUser.email) {
|
|
// Check if new email is already taken by another user
|
|
const existingUser = await prisma.user.findUnique({
|
|
where: { email },
|
|
});
|
|
|
|
if (existingUser && existingUser.id !== userId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Email is already registered to another account',
|
|
});
|
|
}
|
|
|
|
// Email is being changed - require re-verification
|
|
updateData.email = email;
|
|
updateData.emailVerified = false;
|
|
updateData.verificationToken = generateVerificationToken();
|
|
updateData.verificationCode = generateVerificationCode();
|
|
updateData.verificationTokenExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
|
|
|
emailChanged = true;
|
|
}
|
|
|
|
// Update user
|
|
const updatedUser = await prisma.user.update({
|
|
where: { id: userId },
|
|
data: updateData,
|
|
});
|
|
|
|
// If email changed, send verification email
|
|
if (emailChanged) {
|
|
try {
|
|
await sendVerificationEmail(
|
|
updatedUser.email,
|
|
sanitizeForEmail(updatedUser.firstName || updatedUser.username),
|
|
updatedUser.verificationToken,
|
|
updatedUser.verificationCode
|
|
);
|
|
} catch (emailError) {
|
|
console.error('Failed to send verification email:', emailError);
|
|
// Continue anyway - user is updated but email might not have been sent
|
|
}
|
|
}
|
|
|
|
// Generate new JWT token (in case emailVerified changed)
|
|
const token = generateToken({ userId: updatedUser.id });
|
|
|
|
// Remove sensitive data
|
|
const { passwordHash, verificationToken, verificationCode, verificationTokenExpiry, resetToken, resetTokenExpiry, ...userWithoutPassword } = updatedUser;
|
|
|
|
res.json({
|
|
success: true,
|
|
message: emailChanged
|
|
? 'Profile updated. Please verify your new email address.'
|
|
: 'Profile updated successfully',
|
|
data: {
|
|
user: userWithoutPassword,
|
|
token,
|
|
emailChanged,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change password
|
|
* PATCH /api/users/me/password
|
|
*/
|
|
async function changePassword(req, res, next) {
|
|
try {
|
|
const userId = req.user.id;
|
|
const { currentPassword, newPassword } = req.body;
|
|
|
|
if (!currentPassword || !newPassword) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Current password and new password are required',
|
|
});
|
|
}
|
|
|
|
// Get user with password
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
});
|
|
|
|
// Verify current password
|
|
const isPasswordValid = await comparePassword(currentPassword, user.passwordHash);
|
|
if (!isPasswordValid) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: 'Current password is incorrect',
|
|
});
|
|
}
|
|
|
|
// Hash new password
|
|
const hashedPassword = await hashPassword(newPassword);
|
|
|
|
// Update password
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { passwordHash: hashedPassword },
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Password changed successfully',
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
updateProfile,
|
|
changePassword,
|
|
};
|