const db = require("../models"); const bcrypt = require("bcryptjs"); const EstablishmentUser = db.EstablishmentUser; const Establishment = db.Establishment; const crypto = require("crypto"); const { sendEmailService } = require("../services/email.service"); const { sanitizeForLog } = require("../utils/sanitize"); const logger = require("../services/logger"); const User = db.user; function formatName(name) { return name .replace(/([A-Z])/g, ' $1') .replace(/\b\w/g, char => char.toUpperCase()) .trim(); } // Create User exports.createUser = async (req, res) => { try { let { establishment_id, name, email, password, gender } = req.body; establishment_id = Number(establishment_id); if (!Number.isInteger(establishment_id) || establishment_id <= 0) { return res.status(400).json({ status: "error", message: "Invalid establishment_id" }); } const establishment = await Establishment.findByPk(establishment_id); if (!establishment) { return res.status(404).json({ status: "error", message: "Establishment not found" }); } if (typeof email !== "string" || email.trim().length === 0) { return res.status(400).json({ status: "error", message: "Email is required" }); } email = email.trim().toLowerCase(); const existingUser = await EstablishmentUser.findOne({ where: { email, is_active: true } }); const checkEmailInAdminUser = await User.findOne({ where: { email, is_active: true } }); if (existingUser) { return res.status(409).json({ status: "error", message: "Email already exists" }); } if (checkEmailInAdminUser) { logger.info(`Email already exists in Admin Users: ${email}`); return res.status(409).send({ status: "failed", message: "This email address is already registered in the admin users." }); } if (typeof password !== "string" || password.length < 6) { return res.status(400).json({ status: "error", message: "Password must be at least 6 characters long" }); } name = typeof name === "string" ? name.trim() : null; if (gender) { gender = gender.toString().toLowerCase(); if (!["male", "female", "other"].includes(gender)) { return res.status(400).json({ status: "error", message: "Invalid gender" }); } gender = gender.charAt(0).toUpperCase() + gender.slice(1); } // Store plain password for email before hashing const plainPasswordForEmail = password; const hashedPassword = await bcrypt.hash(password, 10); const user = await EstablishmentUser.create({ establishment_id, name, email, password: hashedPassword, gender, created_by: req.user.id }); // Prepare sanitized data for email notification const emailContactName = name; const emailUsername = email; const emailPortalUrl = process.env.FE_BASE_URL; const emailSupportEmail = process.env.SUPPORT_EMAIL; const emailSupportPhone = process.env.SUPPORT_PHONE; const emailData = { contact_name: formatName(emailContactName), portal_url: emailPortalUrl, username: emailUsername, password: plainPasswordForEmail, support_email: emailSupportEmail, support_phone: emailSupportPhone, logo_url: `${process.env.APP_BASE_URL}/assets/FCSCLogo.png` }; await sendEmailService(emailUsername, "establishment_user_creation_to_user", emailData); logger.info(`Establishment user creation email triggered for: ${sanitizeForLog(email)}`); // Prepare secure response - only non-sensitive data const responseUserName = user.name; const responseUserId = user.id; return res.status(201).json({ status: "success", message: "Establishment User created successfully.", }); } catch (error) { logger.error(error.message); logger.error(`Stack trace: ${error.stack}`); return res.status(500).json({ status: "error", message: "Internal server error" }); } }; // Get all users exports.getAllUsers = async (req, res) => { try { let users; if (typeof req.query.establishment_id !== 'undefined') { const establishmentId = parseInt(req.query.establishment_id, 10); if (Number.isNaN(establishmentId)) { return res.status(400).json({ status: "failed", message: "Invalid establishment_id" }); } users = await EstablishmentUser.findAll({ where:{ establishment_id : establishmentId} }); }else{ users = await EstablishmentUser.findAll(); } return res.status(200).send({'status':"success",'message':"Fetched successfully",'data': users }); } catch (err) { logger.error(err.message); logger.error(`Stack trace: ${err.stack}`); return res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; // Get user by id exports.getUserById = async (req, res) => { try { const user = await EstablishmentUser.findByPk(req.params.id); if (!user) res.status(404).send({'status':"failed",'message':"Record not found",'data': "" }); return res.status(200).send({'status':"success",'message':"Fetched successfully",'data': user }); } catch (err) { logger.error(err.message); logger.error(`Stack trace: ${err.stack}`); return res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; // Update user exports.updateUser = async (req, res) => { try { const { name, email, is_active, password } = req.body; const data = {}; // Allow updates only if fields are provided if (name !== undefined && name !== null) data.name = name; if (email !== undefined && email !== null) data.email = email; if (is_active !== undefined && is_active !== null) data.is_active = is_active; if (password && password.trim() !== "") data.password = await bcrypt.hash(password, 10); data.updated_by = req.body.updated_by || req.user.id; data.updated_at = req.body.updated_at || new Date(); const [updated] = await EstablishmentUser.update(data, { where: { id: req.params.id }, }); if (!updated) return res.status(404).send({'status':"failed",'message':"Record not found",'data': "" }); return res.status(200).send({'status':"success",'message':"Record updated successfully",'data': "" }); } catch (err) { logger.error(err.message); logger.error(`Stack trace: ${err.stack}`); return res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; // Change Establishment User Password exports.changeUserPassword = async (req, res) => { try { const { old_password, new_password, confirm_password } = req.body; const userId = req.params.id; // Validate inputs if (!old_password || !new_password || !confirm_password) { return res.status(400).send({ status: "failed", message: "All password fields are required" }); } if (new_password !== confirm_password) { return res.status(400).send({ status: "failed", message: "New password and confirm password do not match" }); } // Find user const user = await EstablishmentUser.findByPk(userId); if (!user) { return res.status(404).send({ status: "failed", message: "User not found" }); } // Verify old password const isMatch = await bcrypt.compare(old_password, user.password); if (!isMatch) { return res.status(401).send({ status: "failed", message: "Old password is incorrect" }); } // Hash and update new password const hashedPassword = await bcrypt.hash(new_password, 10); await EstablishmentUser.update( { password: hashedPassword, updated_by: req.user.id, updated_at: new Date() }, { where: { id: userId } } ); return res.status(200).send({ status: "success", message: "Password updated successfully" }); } catch (err) { logger.error(err.message); logger.error(`Stack trace: ${err.stack}`); return res.status(500).send({ status: "failed", message: "An unexpected error occurred" }); } }; // Delete user exports.deleteUser = async (req, res) => { try { const deleted = await EstablishmentUser.destroy({ where: { id: req.params.id } }); if (!deleted) return res.status(404).send({'status':"failed",'message':"Record not found",'data': "" }); return res.status(200).send({'status':"success",'message':"Record deleted successfully",'data': "" }); } catch (err) { logger.error(err.message); logger.error(`Stack trace: ${err.stack}`); return res.status(500).send({'status':"failed",'message': "Internal server error" }); } }; // Trigger user creation email exports.triggerEstablishmentUsersWelcomeEmail = async (req, res) => { try { let { establishment_id } = req.body; establishment_id = Number(establishment_id); if (!Number.isInteger(establishment_id) || establishment_id <= 0) { return res.status(400).json({ status: "error", message: "Invalid establishment_id", }); } const establishment = await Establishment.findByPk(establishment_id); if (!establishment) { return res.status(404).json({ status: "error", message: "Establishment not found", }); } const users = await EstablishmentUser.findAll({ where: { establishment_id, is_active: true, }, }); count = 0; for (const user of users) { // Only for users who never logged in if (user.last_login === null) { count = count + 1; // ✅ Generate random 8 character password const password = crypto.randomBytes(4).toString("hex"); // 8 chars const hashedPassword = await bcrypt.hash(password, 10); // ✅ Update password await EstablishmentUser.update( { password: hashedPassword }, { where: { id: user.id } } ); // ✅ Send email await sendEmailService( user.email, "establishment_user_creation_to_user", { contact_name: formatName(user.name), portal_url: process.env.FE_BASE_URL, username: user.email, password: password, support_email: process.env.SUPPORT_EMAIL, support_phone: process.env.SUPPORT_PHONE, logo_url: `${process.env.APP_BASE_URL}/assets/FCSCLogo.png` } ); } } if(count != 0) { return res.status(200).json({status: "success",message: "Email sent successfully.",}); }else{ return res.status(404).json({status: "failed",message: "Users have already logged in",}); } } catch (error) { console.error(error); logger.error(error.message); logger.error(`Stack trace: ${error.stack}`); return res.status(500).json({ status: "error", message: "Internal server error" }); } };