const { User, UserAddress } = require("../models"); const emailServices = require("./email"); const logger = require("../utils/logger"); /** * UserService handles user-related business logic * Including profile updates and associated notifications */ class UserService { /** * Update user profile and send notification if personal info changed * @param {string} userId - User ID * @param {Object} rawUpdateData - Data to update * @param {Object} options - Optional transaction or other options * @returns {Promise} Updated user (without password field) */ async updateProfile(userId, rawUpdateData, options = {}) { const user = await User.findByPk(userId); if (!user) { throw new Error("User not found"); } // Store original values for comparison const originalValues = { email: user.email, firstName: user.firstName, lastName: user.lastName, address1: user.address1, address2: user.address2, city: user.city, state: user.state, zipCode: user.zipCode, country: user.country, }; // Prepare update data with preprocessing const updateData = { ...rawUpdateData }; // Only include email if it's not empty if (updateData.email !== undefined) { if (updateData.email && updateData.email.trim() !== "") { updateData.email = updateData.email.trim(); } else { delete updateData.email; // Don't update if empty } } // Handle phone: convert empty strings to null to avoid unique constraint issues if (updateData.phone !== undefined) { updateData.phone = updateData.phone && updateData.phone.trim() !== "" ? updateData.phone.trim() : null; } // Perform the update await user.update(updateData, options); // Check if personal information changed const personalInfoFields = [ "email", "firstName", "lastName", "address1", "address2", "city", "state", "zipCode", "country", ]; const changedFields = personalInfoFields.filter( (field) => updateData[field] !== undefined && originalValues[field] !== updateData[field] ); // Send notification email if personal info changed if (changedFields.length > 0 && process.env.NODE_ENV !== "test") { try { await emailServices.auth.sendPersonalInfoChangedEmail(user); logger.info("Personal information changed notification sent", { userId: user.id, email: user.email, changedFields, }); } catch (emailError) { logger.error( "Failed to send personal information changed notification", { error: emailError.message, stack: emailError.stack, userId: user.id, email: user.email, changedFields, } ); // Don't throw - email failure shouldn't fail the update } } // Return user without password const updatedUser = await User.findByPk(user.id, { attributes: { exclude: ["password"] }, }); return updatedUser; } /** * Create a new address for a user and send notification * @param {string} userId - User ID * @param {Object} addressData - Address data * @returns {Promise} Created address */ async createUserAddress(userId, addressData) { const user = await User.findByPk(userId); if (!user) { throw new Error("User not found"); } const address = await UserAddress.create({ ...addressData, userId, }); // Send notification for address creation if (process.env.NODE_ENV !== "test") { try { await emailServices.auth.sendPersonalInfoChangedEmail(user); logger.info( "Personal information changed notification sent (address created)", { userId: user.id, email: user.email, addressId: address.id, } ); } catch (emailError) { logger.error("Failed to send notification for address creation", { error: emailError.message, stack: emailError.stack, userId: user.id, addressId: address.id, }); } } return address; } /** * Update a user address and send notification * @param {string} userId - User ID * @param {string} addressId - Address ID * @param {Object} updateData - Data to update * @returns {Promise} Updated address */ async updateUserAddress(userId, addressId, updateData) { const address = await UserAddress.findOne({ where: { id: addressId, userId }, }); if (!address) { throw new Error("Address not found"); } await address.update(updateData); // Send notification for address update if (process.env.NODE_ENV !== "test") { try { const user = await User.findByPk(userId); await emailServices.auth.sendPersonalInfoChangedEmail(user); logger.info( "Personal information changed notification sent (address updated)", { userId: user.id, email: user.email, addressId: address.id, } ); } catch (emailError) { logger.error("Failed to send notification for address update", { error: emailError.message, stack: emailError.stack, userId, addressId: address.id, }); } } return address; } /** * Delete a user address and send notification * @param {string} userId - User ID * @param {string} addressId - Address ID * @returns {Promise} */ async deleteUserAddress(userId, addressId) { const address = await UserAddress.findOne({ where: { id: addressId, userId }, }); if (!address) { throw new Error("Address not found"); } await address.destroy(); // Send notification for address deletion if (process.env.NODE_ENV !== "test") { try { const user = await User.findByPk(userId); await emailServices.auth.sendPersonalInfoChangedEmail(user); logger.info( "Personal information changed notification sent (address deleted)", { userId: user.id, email: user.email, addressId, } ); } catch (emailError) { logger.error("Failed to send notification for address deletion", { error: emailError.message, stack: emailError.stack, userId, addressId, }); } } } } module.exports = new UserService();