517 lines
12 KiB
JavaScript
517 lines
12 KiB
JavaScript
/**
|
|
* Copyright (c) 2026 Niolla
|
|
* All rights reserved.
|
|
*
|
|
* This source code is proprietary and confidential.
|
|
* Unauthorized copying, modification, distribution, or use
|
|
* of this file, via any medium, is strictly prohibited.
|
|
*/
|
|
|
|
// app/controllers/user.controller.js
|
|
|
|
const db = require("../models");
|
|
const { hashPassword } = require("../utils/hashPassword.util");
|
|
const {
|
|
validatePassword,
|
|
} = require("../utils/validation/validatePassword.util");
|
|
const { validateEmail } = require("../utils/validation/validateEmail.util");
|
|
const { generateUserId, generateId } = require("../utils/idGen.util");
|
|
const {
|
|
createCustomerDetails,
|
|
} = require("../utils/users/createCustomerDetails.util");
|
|
const {
|
|
createBusinessCustomerDetails,
|
|
} = require("../utils/users/createBusinessCustomer.util");
|
|
const { logActivity } = require("../services/activity.service");
|
|
const { sendMail } = require("../utils/mail.util");
|
|
const {
|
|
createEmailVerification,
|
|
verifyEmailVerificationToken,
|
|
sendVerificationEmail,
|
|
deleteEmailVerification,
|
|
} = require("../utils/emailVerification.util");
|
|
const User = db.User;
|
|
const Profile = db.Profile;
|
|
|
|
// Create a new user
|
|
exports.createNewUser = async (req, res) => {
|
|
const transaction = await db.sequelize.transaction();
|
|
|
|
try {
|
|
const {
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
password,
|
|
accountType,
|
|
address,
|
|
phoneNumber,
|
|
businessName,
|
|
businessRegistrationNumber,
|
|
businessType,
|
|
contactName,
|
|
businessEmail,
|
|
expectedMonthlyVolume,
|
|
note,
|
|
} = req.body;
|
|
|
|
if (!firstName || !lastName || !email || !password || !accountType) {
|
|
await transaction.rollback();
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
message:
|
|
"First name, last name, email, password and account type are required",
|
|
});
|
|
}
|
|
|
|
if (accountType !== "customer" && accountType !== "business_customer") {
|
|
await transaction.rollback();
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
message:
|
|
"Invalid account type. Must be either 'customer' or 'business_customer'",
|
|
});
|
|
}
|
|
|
|
const emailValid = validateEmail(email);
|
|
|
|
if (!emailValid) {
|
|
await transaction.rollback();
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
message: "Invalid email address",
|
|
});
|
|
}
|
|
|
|
const userExists = await User.findOne({ where: { email } });
|
|
if (userExists) {
|
|
await transaction.rollback();
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
message: "User with this email already exists",
|
|
});
|
|
}
|
|
|
|
const validatePasswordResult = validatePassword(password);
|
|
|
|
if (!validatePasswordResult) {
|
|
await transaction.rollback();
|
|
return res.status(400).send({
|
|
success: false,
|
|
message: "Password does not meet the required criteria",
|
|
});
|
|
}
|
|
|
|
const hashedPassword = await hashPassword(password);
|
|
|
|
const userID = generateUserId();
|
|
|
|
const newUser = await User.create(
|
|
{
|
|
id: userID,
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
password: hashedPassword,
|
|
accountType,
|
|
accountStatus: "PENDING_VERIFICATION",
|
|
emailVerifiedAt: null,
|
|
},
|
|
{ transaction },
|
|
);
|
|
|
|
const newProfile = await Profile.create(
|
|
{
|
|
profile_id: generateId(),
|
|
user_id: newUser.id,
|
|
theme: "light",
|
|
notificationsEnabled: true,
|
|
profilePicture_id: "N/A",
|
|
backgroundImage_id: "N/A",
|
|
dob: null,
|
|
phone_number: null,
|
|
},
|
|
{ transaction },
|
|
);
|
|
|
|
//create customer and business customer
|
|
if (accountType === "customer") {
|
|
const customerData = {
|
|
address,phoneNumber,
|
|
};
|
|
|
|
await createCustomerDetails(
|
|
newUser.id,
|
|
customerData,
|
|
transaction,
|
|
);
|
|
} else if (accountType === "business_customer") {
|
|
const businessData = {
|
|
businessName,
|
|
businessRegistrationNumber,
|
|
businessType,
|
|
contactName,
|
|
phoneNumber,
|
|
businessEmail,
|
|
expectedMonthlyVolume,
|
|
note,
|
|
};
|
|
|
|
await createBusinessCustomerDetails(
|
|
newUser.id,
|
|
businessData,
|
|
transaction,
|
|
);
|
|
}
|
|
|
|
await transaction.commit();
|
|
|
|
const verificationToken = await createEmailVerification(newUser.id);
|
|
|
|
try {
|
|
await sendVerificationEmail(
|
|
newUser.email,
|
|
newUser.firstName,
|
|
verificationToken,
|
|
);
|
|
} catch (error) {
|
|
console.error("Error sending verification email:", error);
|
|
}
|
|
|
|
await logActivity({
|
|
user: newUser,
|
|
description: `Created New User with ID: ${newUser.id}`,
|
|
type: "CREATE_USER",
|
|
module: "User Management",
|
|
});
|
|
|
|
res.status(201).send({
|
|
success: true,
|
|
message: "User Created Successfully",
|
|
data: {
|
|
id: newUser.id,
|
|
firstName: newUser.firstName,
|
|
lastName: newUser.lastName,
|
|
email: newUser.email,
|
|
accountType: newUser.accountType,
|
|
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (!transaction.finished) {
|
|
await transaction.rollback();
|
|
}
|
|
|
|
console.error("CREATE USER ERROR:", error);
|
|
|
|
res.status(500).send({
|
|
success: false,
|
|
message: "Failed to create user",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Verify customer email
|
|
exports.verifyEmail = async (req, res) => {
|
|
const transaction = await db.sequelize.transaction();
|
|
|
|
try {
|
|
const { token } = req.body;
|
|
|
|
if (!token) {
|
|
await transaction.rollback();
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
|
|
message: "Verification token is required",
|
|
});
|
|
}
|
|
|
|
const verification = await verifyEmailVerificationToken(token);
|
|
|
|
if (!verification) {
|
|
await transaction.rollback();
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
|
|
message: "Verification token is invalid or expired",
|
|
});
|
|
}
|
|
|
|
const { userId, redisKey } = verification;
|
|
|
|
const user = await User.findOne({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
|
|
transaction,
|
|
});
|
|
|
|
if (!user) {
|
|
await transaction.rollback();
|
|
|
|
await deleteEmailVerification(redisKey);
|
|
|
|
return res.status(404).send({
|
|
success: false,
|
|
|
|
message: "User not found",
|
|
});
|
|
}
|
|
|
|
if (user.accountStatus === "ACTIVE" && user.emailVerifiedAt) {
|
|
await transaction.rollback();
|
|
|
|
// Token is no longer needed
|
|
await deleteEmailVerification(redisKey);
|
|
|
|
return res.status(400).send({
|
|
success: false,
|
|
|
|
message: "Email is already verified",
|
|
});
|
|
}
|
|
|
|
user.accountStatus = "ACTIVE";
|
|
|
|
user.emailVerifiedAt = new Date();
|
|
|
|
await user.save({
|
|
transaction,
|
|
});
|
|
|
|
await transaction.commit();
|
|
|
|
await deleteEmailVerification(redisKey);
|
|
|
|
return res.status(200).send({
|
|
success: true,
|
|
|
|
message: "Email verified successfully. Your account is now active.",
|
|
});
|
|
} catch (error) {
|
|
if (!transaction.finished) {
|
|
await transaction.rollback();
|
|
}
|
|
|
|
console.error("VERIFY EMAIL ERROR:", error);
|
|
|
|
return res.status(500).send({
|
|
success: false,
|
|
|
|
message: "Failed to verify email",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get users with pagination (20 per page)
|
|
exports.getAllUsers = async (req, res) => {
|
|
try {
|
|
const page = parseInt(req.query.page) || 1; // default page = 1
|
|
const limit = 20;
|
|
const offset = (page - 1) * limit;
|
|
|
|
const { count, rows: users } = await User.findAndCountAll({
|
|
attributes: { exclude: ["password"] },
|
|
limit,
|
|
offset,
|
|
order: [["createdAt", "DESC"]], // optional sorting
|
|
});
|
|
|
|
res.status(200).send({
|
|
success: true,
|
|
data: users,
|
|
pagination: {
|
|
totalUsers: count,
|
|
totalPages: Math.ceil(count / limit),
|
|
currentPage: page,
|
|
pageSize: limit,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
res.status(500).send({
|
|
success: false,
|
|
message: "Failed to retrieve users",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get user details by ID
|
|
exports.getUserById = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const user = await User.findOne({
|
|
where: { id },
|
|
attributes: { exclude: ["password"] },
|
|
include: [{ model: Profile, as: "profile" }],
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(404).send({
|
|
success: false,
|
|
message: "User not found",
|
|
});
|
|
}
|
|
|
|
res.status(200).send({
|
|
success: true,
|
|
data: user,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).send({
|
|
success: false,
|
|
message: "Failed to retrieve user",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Update user details
|
|
exports.updateUser = async (req, res) => {
|
|
const transaction = await db.sequelize.transaction();
|
|
try {
|
|
const { id } = req.params;
|
|
const {
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
role,
|
|
roleID,
|
|
accountType,
|
|
department,
|
|
theme,
|
|
notificationsEnabled,
|
|
profilePicture_id,
|
|
backgroundImage_id,
|
|
dob,
|
|
phone_number,
|
|
} = req.body;
|
|
const user = await User.findOne({
|
|
where: { id },
|
|
});
|
|
const UserProfile = await Profile.findOne({
|
|
where: { user_id: id },
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(404).send({
|
|
success: false,
|
|
message: "User not found",
|
|
});
|
|
}
|
|
|
|
if (!UserProfile) {
|
|
return res.status(404).send({
|
|
success: false,
|
|
message: "User profile not found",
|
|
});
|
|
}
|
|
|
|
user.firstName = firstName || user.firstName;
|
|
user.lastName = lastName || user.lastName;
|
|
user.role = role || user.role;
|
|
user.roleID = roleID || user.roleID || "N/A";
|
|
user.accountType = accountType || user.accountType;
|
|
user.department = department || user.department;
|
|
|
|
UserProfile.theme = theme || UserProfile.theme;
|
|
UserProfile.notificationsEnabled =
|
|
notificationsEnabled !== undefined
|
|
? notificationsEnabled
|
|
: UserProfile.notificationsEnabled;
|
|
UserProfile.profilePicture_id =
|
|
profilePicture_id || UserProfile.profilePicture_id || "N/A";
|
|
UserProfile.backgroundImage_id =
|
|
backgroundImage_id || UserProfile.backgroundImage_id || "N/A";
|
|
UserProfile.dob = dob || UserProfile.dob;
|
|
UserProfile.phone_number = phone_number || UserProfile.phone_number;
|
|
|
|
await UserProfile.save({ transaction });
|
|
await user.save({ transaction });
|
|
await transaction.commit();
|
|
|
|
await logActivity({
|
|
user: req.user,
|
|
description: `Updated User with ID: ${user.id}`,
|
|
type: "UPDATE_USER",
|
|
module: "User Management",
|
|
});
|
|
|
|
const data = {
|
|
id: user.id,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
accountType: user.accountType,
|
|
role: user.role,
|
|
department: user.department,
|
|
profile: {
|
|
theme: UserProfile.theme,
|
|
notificationsEnabled: UserProfile.notificationsEnabled,
|
|
profilePicture_id: UserProfile.profilePicture_id,
|
|
backgroundImage_id: UserProfile.backgroundImage_id,
|
|
dob: UserProfile.dob,
|
|
phone_number: UserProfile.phone_number,
|
|
},
|
|
};
|
|
|
|
res.status(200).send({
|
|
success: true,
|
|
message: "User updated successfully",
|
|
data,
|
|
});
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
res.status(500).send({
|
|
success: false,
|
|
message: `Error: ${error.message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Delete user
|
|
exports.deleteUser = async (req, res) => {
|
|
const transaction = await db.sequelize.transaction();
|
|
try {
|
|
const { id } = req.params;
|
|
const user = await User.findOne({
|
|
where: { id },
|
|
});
|
|
if (!user) {
|
|
return res.status(404).send({
|
|
success: false,
|
|
message: "User not found",
|
|
});
|
|
}
|
|
await user.destroy({ transaction });
|
|
await transaction.commit();
|
|
|
|
await logActivity({
|
|
user: req.user,
|
|
description: `Deleted User with ID: ${user.id}`,
|
|
type: "DELETE_USER",
|
|
module: "User Management",
|
|
});
|
|
|
|
res.status(200).send({
|
|
success: true,
|
|
message: "User deleted successfully",
|
|
});
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
res.status(500).send({
|
|
success: false,
|
|
message: `Error: ${error.message}`,
|
|
});
|
|
}
|
|
};
|