spurrin-backend/src/controllers/appUserController.js
2025-08-01 13:18:38 +05:30

1827 lines
54 KiB
JavaScript

const bcrypt = require("bcrypt");
const db = require("../config/database"); // Database connection
const fs = require("fs");
const jwt = require("jsonwebtoken");
const nlp = require("compromise");
const nodemailer = require("nodemailer");
const sender_port = process.env.SENDER_PORT;
const sender_security = process.env.SENDER_SECURITY;
const path = require('path')
const { emitEvent } = require("../services/secondaryWebsocket");
console.log('sender port : ', sender_port);
console.log('sender security : ', sender_security);
// zoho transporter
const transporter = nodemailer.createTransport({
host: "smtp.zoho.com",
port: 465,
secure: true,
auth: {
user: "kavya.j@tech4biz.io", // Your Zoho email address
pass: "8pQfkBw8gbrz", // Your Zoho App Password (not your account password)
}
});
exports.initWebSocket = (io) => {
io.on("connection", (socket) => {
console.log("Hospital connected:", socket.id);
socket.on("register_hospital", (hospitalCode) => {
hospitalSockets[hospitalCode] = socket;
console.log(`Hospital ${hospitalCode} registered.`);
});
socket.on("disconnect", () => {
const hospitalCode = Object.keys(hospitalSockets).find(
(key) => hospitalSockets[key] === socket
);
if (hospitalCode) delete hospitalSockets[hospitalCode];
console.log(`Hospital ${hospitalCode} disconnected.`);
});
});
};
exports.uploadIdPhoto = async (req, res) => {
try {
// Extracting user ID and hospital code
const userId = parseInt(req.params.id, 10); // User ID from route
const { hospital_code } = req.user; // Authenticated user's hospital code
// Check if user ID is valid
if (isNaN(userId)) {
console.error("Invalid User ID:", userId);
return res.status(400).json({ error: "Invalid user ID" });
}
// Check if a file was uploaded
if (!req.file) {
console.error("No file uploaded");
return res.status(400).json({ error: "No file uploaded" });
}
const photoPath = `/uploads/id_photos/${req.file.filename}`;
// Verify app user is part of the same hospital
const query = `
SELECT hospital_code FROM app_users WHERE id = ? AND hospital_code = ?
`;
const result = await db.query(query, [userId, hospital_code]);
if (result.length === 0) {
console.error(
"Authorization failed for user ID:",
userId,
"with hospital_code:",
hospital_code
);
return res
.status(403)
.json({ error: "You are not authorized to upload ID for this user" });
}
// Update the ID photo path in the database
await db.query("UPDATE app_users SET id_photo_url = ? WHERE id = ?", [
photoPath,
userId,
]);
res.status(200).json({
message: "ID photo uploaded successfully!",
id_photo_url: photoPath,
});
} catch (error) {
console.error("Error uploading ID photo:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.updateSettings = async (req, res) => {
try {
const userId = req.user.id; // Assuming user is authenticated and userId is available
const { pin, pin_enabled, remember_me } = req.body;
// Validate pin (if provided)
if (pin && (typeof pin !== 'string' || pin.length !== 4)) {
return res.status(400).json({ error: 'Invalid PIN format. Must be a 4-digit string.' });
}
// Determine expiry based on remember_me
let expiresIn = '5h';
let expiryTimestamp = new Date();
if (remember_me) {
expiresIn = '30d';
expiryTimestamp.setDate(expiryTimestamp.getDate() + 30);
} else {
expiryTimestamp.setHours(expiryTimestamp.getHours() + 5);
}
// Generate new access token
const payload = { id: userId, role: 'AppUser' };
const accessToken = jwt.sign(payload, process.env.JWT_ACCESS_TOKEN_SECRET, {
expiresIn: expiresIn,
});
// Update user settings and access token
const query = `UPDATE app_users SET pin_number = ?, pin_enabled = ?, remember_me = ?, access_token = ?, access_token_expiry = ? WHERE id = ?`;
const result=await db.query(query, [pin, pin_enabled, remember_me, accessToken, expiryTimestamp, userId]);
if (result.affectedRows > 0) {
console.log("Table updated successfully.");
} else {
console.log("Table not updated. No matching user found.");
}
res.status(200).json({ message: 'Settings updated successfully.', accessToken });
} catch (error) {
console.error('Error updating settings:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
exports.hitlike = async (req, res) => {
try {
// Extract user ID and session ID from the request
const { session_id, id } = req.body;
const log_id = id;
const app_user_id = req.user.id;
// Check if both app_user_id and session_id are provided
if (!app_user_id || !session_id || !log_id) {
return res.status(400).json({
status: 'error',
message: 'app user id and session id and log id are required',
});
}
// Query to toggle the is_liked value
const toggleQuery = `
UPDATE interaction_logs
SET is_liked = NOT is_liked
WHERE app_user_id = ? AND session_id = ? AND id = ?
`;
// Execute the query
const result = await db.query(toggleQuery, [app_user_id, session_id,log_id]);
// Check if any rows were affected
if (result.affectedRows > 0) {
return res.status(200).json({
status: 'success',
message: 'Like updated successfully',
data: {
app_user_id,
session_id,
is_liked: result.changedRows > 0 ? 1 : 0, // 1 if changed, 0 if not
},
});
} else {
return res.status(404).json({
status: 'error',
message: 'No matching record found to toggle',
});
}
} catch (error) {
console.error('Error during like toggle:', error);
return res.status(500).json({
status: 'error',
message: 'Internal server error',
});
}
};
exports.hitFlag = async (req, res) => {
try {
// Extract user ID and session ID from the request
const { session_id, logid } = req.body;
const log_id = logid;
const app_user_id = req.user.id;
// Check if both app_user_id and session_id are provided
if (!app_user_id || !session_id || !log_id) {
return res.status(400).json({
status: 'error',
message: 'app user id and session id and log id are required',
});
}
// Query to toggle the is_liked value
const toggleQuery = `
UPDATE interaction_logs
SET is_flagged = NOT is_flagged
WHERE app_user_id = ? AND session_id = ? AND id = ?
`;
// Execute the query
const result = await db.query(toggleQuery, [app_user_id, session_id,log_id]);
// Check if any rows were affected
if (result.affectedRows > 0) {
return res.status(200).json({
status: 'success',
message: 'Updated successfully',
data: {
app_user_id,
session_id
},
});
} else {
return res.status(404).json({
status: 'error',
message: 'No matching record found to toggle',
});
}
} catch (error) {
console.error('Error during like toggle:', error);
return res.status(500).json({
status: 'error',
message: 'Internal server error',
});
}
};
exports.addReportText = async (req, res) => {
try {
const { session_id, report_text } = req.body;
const app_user_id = req.user.id;
// Validate input
if (!app_user_id || !session_id || typeof report_text !== 'string') {
return res.status(400).json({
status: 'error',
message: 'app_user_id, session_id, logid, and report_text are required',
});
}
// Update query
const updateQuery = `
UPDATE interaction_logs
SET report_text = ?
WHERE app_user_id = ? AND session_id = ?
`;
// Execute update
const result = await db.query(updateQuery, [report_text, app_user_id, session_id]);
// Handle response
if (result.affectedRows > 0) {
return res.status(200).json({
status: 'success',
message: 'Session reported successfully. Admin will review your report',
data: {
app_user_id,
session_id,
report_text
},
});
} else {
return res.status(404).json({
status: 'error',
message: 'No matching record found to update report text',
});
}
} catch (error) {
console.error('Error updating report text:', error);
return res.status(500).json({
status: 'error',
message: 'Internal server error',
});
}
};
exports.updateChecked = async (req,res) =>{
try {
const app_user_id = req.params.id;
if (!["Admin", 8].includes(req.user.role)) {
return res.status(403).json({ error: "Unauthorized to acknowledge" });
}
const updateQuery = `
UPDATE app_users
SET checked = ?
WHERE id = ?
`;
const result = await db.query(updateQuery, [1, app_user_id]);
if (result.affectedRows > 0) {
return res.status(200).json({
status: 'success',
message: 'Acknowledged successfully',
});
} else {
return res.status(404).json({
status: 'error',
message: 'No matching record found to update checked',
});
}
} catch (error) {
console.error("Error updating checked:", error);
return res.status(500).json({ error: "Internal server error" });
}
}
exports.signup = async (req, res) => {
try {
const { pin, pin_status,remember_me, email, password, hospital_code, username } = req.body;
if (!email || !password || !hospital_code || !username) {
return res.status(400).json({
error: "Email, password, username, and hospital code are required",
});
}
const pin_enabled = (pin_status === undefined || pin_status === '') ? 0 : pin_status;
const remember_me_ = (remember_me === undefined || remember_me === '') ? 0 : remember_me;
// Check if the hospital_code exists in the `hospitals` table
const hospitalQuery =
"SELECT hospital_code FROM hospitals WHERE hospital_code = ?";
const hospitalResult = await db.query(hospitalQuery, [hospital_code]);
if (hospitalResult.length === 0) {
return res.status(404).json({ error: "Invalid hospital code" });
}
// Check if the email is already in use
const userQuery = "SELECT id FROM app_users WHERE email = ?";
const userResult = await db.query(userQuery, [email]);
if (userResult.length > 0) {
return res.status(400).json({ error: "Email already in use" });
}
// Check if a file was uploaded
if (!req.file) {
return res.status(400).json({ error: "ID photo url is required" });
}
// Hash the password
const hashPassword = await bcrypt.hash(password, 10);
// Insert the new app user into the `app_users` table
const insertQuery = `
INSERT INTO app_users (email, hash_password, hospital_code, status, username, pin_number, pin_enabled, remember_me )
VALUES (?, ?, ?, 'Pending', ?, ?, ?,?)
`;
const result = await db.query(insertQuery, [
email,
hashPassword,
hospital_code,
username,
pin, // Set pin to null if not provided
pin_enabled,
remember_me_
]);
const userId = result.insertId; // Get the new user's ID
const filePath = `/uploads/id_photos/${req.file.filename}`; // Correct file path
// Update the ID photo path in the database
await db.query(
"UPDATE app_users SET id_photo_url = ?, upload_status = ? WHERE id = ?",
[filePath, "1", userId]
);
// ✅ Notify hospitals in the SECONDARY WebSocket instance
emitEvent("new_user", {
message: `A new user has been created for hospital ${hospital_code}.`,
user: { id: userId, username, email, hospital_code: hospital_code },
});
res.status(201).json({
message: "User signed up and ID photo uploaded successfully!",
data: {
id: userId,
email,
hospital_code,
username,
id_photo_url: filePath,
pin: pin || null,
pin_status : pin_status,
remember_me: remember_me
},
});
} catch (error) {
console.error("Error during signup and ID photo upload:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
let remember_me;
if (!email || !password) {
return res.status(400).json({ error: "Email and password are required" });
}
console.log("email----",email)
// Check if the user exists
const query = "SELECT * FROM app_users WHERE email = ?";
const result = await db.query(query, [email]);
console.log("result---",result)
if (result.length === 0) {
return res.status(404).json({ error: "User not found" });
}
const user = result[0];
remember_me = user.remember_me
console.log("user-data----------------------",user);
const hospitalData = await db.query("SELECT * FROM hospitals WHERE hospital_code = ?", [user.hospital_code]);
// if (hospitalData.publicSignupEnabled) {
// throw new Error("Hospital not found");
// }
console.log("hospitalData---",hospitalData)
let actual_status;
actual_status = user.status
if(hospitalData[0].publicSignupEnabled){
user.status = "Active"
}
if (user.status === "Pending" && !hospitalData[0].publicSignupEnabled) {
return res.status(404).json({ error: "Your Account is Under Review" });
}
if (user.status === "Inactive" && !hospitalData[0].publicSignupEnabled) {
return res.status(404).json({ error: "Contact admin your account is not approved" });
}
// Validate the password
const validPassword = await bcrypt.compare(password, user.hash_password);
if (!validPassword) {
return res.status(401).json({ error: "Invalid email or password" });
}
// Determine expiry based on remember_me
let expiresIn = "5h";
let expiryTimestamp = new Date();
let rememberMeValue = remember_me;
if (rememberMeValue === undefined) rememberMeValue = user.remember_me;
if (rememberMeValue === true || rememberMeValue === 1 || rememberMeValue === "1" || rememberMeValue === "true") {
const updateQuery = `
UPDATE app_users
SET remember_me = ?
WHERE id = ?
`;
await db.query(updateQuery, [remember_me,user.id]);
expiresIn = "30d";
expiryTimestamp.setDate(expiryTimestamp.getDate() + 30);
} else {
expiryTimestamp.setHours(expiryTimestamp.getHours() + 5);
}
// Generate a new access token
const payload = { id: user.id, email: user.email, role: "AppUser" };
const accessToken = jwt.sign(payload, process.env.JWT_ACCESS_TOKEN_SECRET, {
expiresIn: expiresIn,
});
// SQL query to fetch hospital data based on the provided hospital_code
const query_hospital = `
SELECT * FROM hospitals WHERE hospital_code = ?;
`;
// Run the query
const result_hospital = await db.query(query_hospital, [user.hospital_code]);
// hospital users
const queryHospitalUsr = `
SELECT * FROM hospital_users WHERE hospital_code = ?;
`;
// Run the query
const resultHospitalUsr = await db.query(queryHospitalUsr, [user.hospital_code]);
const hospitalUser = resultHospitalUsr[0];
if (!hospitalUser) {
return res.status(404).json({ error: "Hospital user not found" });
}
// Update the access token and expiry in the database
const updateQuery = `
UPDATE app_users
SET access_token = ?, access_token_expiry = ?, remember_me = ?
WHERE id = ?
`;
await db.query(updateQuery, [accessToken, expiryTimestamp, (rememberMeValue === true || rememberMeValue === 1 || rememberMeValue === "1" || rememberMeValue === "true") ? 1 : 0, user.id]);
// Send response
res.status(200).json({
message: "Login successful",
user: {
id: user.id,
email: user.email,
pin: user.pin_number,
pin_enabled: user.pin_enabled,
hospital_code: user.hospital_code,
hospital_id : hospitalData[0].id,
status: user.status,
actualStatus: actual_status,
hospital_name: result_hospital[0].name_hospital,
primary_color: result_hospital[0].primary_color,
secondary_color: result_hospital[0].secondary_color,
upload_status: user.upload_status,
id_photo_url: user.id_photo_url,
primary_admin_email: result_hospital[0].primary_admin_email,
mobile_number: result_hospital[0].mobile_number,
username: user.username,
logo_url: resultHospitalUsr[0].profile_photo_url,
query_title: user.query_title,
remember_me: (rememberMeValue === true || rememberMeValue === 1 || rememberMeValue === "1" || rememberMeValue === "true") ? 1 : 0
},
accessToken,
});
} catch (error) {
console.error("Error during login:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.checkPin = async (req, res) => {
try {
const { email, pin } = req.body;
if (!email || !pin) {
return res.status(400).json({ error: "Email and Pin are required" });
}
// Check if the user exists
const query = "SELECT * FROM app_users WHERE email = ?";
const result = await db.query(query, [email]);
if (result.length === 0) {
return res.status(404).json({ error: "User not found" });
}
const user = result[0];
const hospitalData = await db.query("SELECT * FROM hospitals WHERE hospital_code = ?", [user.hospital_code]);
if (user.status === "Pending" && !hospitalData[0].publicSignupEnabled) {
throw new Error("Your Account is Under Review");
}
if (user.status === "Inactive" && !hospitalData[0].publicSignupEnabled) {
throw new Error("Contact admin your account is not approved");
}
// Check PIN if PIN status is enabled
if (pin !== user.pin_number) {
return res.status(401).json({
error: "Invalid PIN",
requires_pin: true,
message: "Incorrect PIN. Please try again"
});
}
// Send response
res.status(200).json({
message: "Pin verified successfully",
});
} catch (error) {
console.error("Error during login:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.logout = async (req, res) => {
try {
const userId = req.user.id; // Assuming the user is authenticated via middleware
// Check if the user exists
const query = "SELECT * FROM app_users WHERE id = ?";
const result = await db.query(query, [userId]);
if (result.length === 0) {
return res.status(404).json({ error: "User not found" });
}
// Invalidate the access token in the database
const updateQuery = `
UPDATE app_users
SET access_token = NULL, access_token_expiry = NULL
WHERE id = ?
`;
await db.query(updateQuery, [userId]);
res.status(200).json({ message: "Logout successful" });
} catch (error) {
console.error("Error during logout:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
// controller.js
exports.getAppUsersByHospitalCode = async (req, res) => {
try {
// Extract the hospital_code from the token (req.user object)
const userId = req.user.id;
if (!userId) {
return res.status(400).json({ error: "User not found for the token" });
}
// Query to fetch hospital users based on the hospital_code
const query = `
SELECT id, email, status,hospital_code
FROM app_users
WHERE id = ?;
`;
const result = await db.query(query, [userId]);
if (result.length === 0) {
return res
.status(404)
.json({ error: "No users found for the specified Token" });
}
const hospitalData = await db.query("SELECT * FROM hospitals WHERE hospital_code = ?", [result[0].hospital_code]);
if(hospitalData[0].publicSignupEnabled){
result[0].status = 'Active'
}
// Send the response with the users data
res.status(200).json({
message: "Hospital users fetched successfully",
user: result[0],
});
} catch (error) {
console.error("Error fetching hospital users:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.approveUserId = async (req, res) => {
try {
const appUserId = req.params.id; // App user ID to approve
const { role } = req.user; // Authenticated user's details
const { Action } = req.body;
const { hospital_code } = req.user;
if (!req.body.Action) {
return res.status(400).json({ error: "Action is required" });
}
if (!["Superadmin", "Admin", 8].includes(role)) {
return res.status(403).json({ error: "Unauthorized to approve IDs" });
}
const query = `
SELECT hospital_code FROM app_users WHERE id = ? AND hospital_code = ?
`;
const result = await db.query(query, [appUserId, hospital_code]);
if (result.length === 0) {
return res
.status(403)
.json({ error: "User does not belong to your hospital" });
}
// Approve the ID
if (Action == "Reject") {
await db.query('UPDATE app_users SET status = "Inactive" WHERE id = ?', [
appUserId,
]);
res.status(200).json({ message: "ID Rejected successfully" });
} else if (Action == "Approve") {
await db.query('UPDATE app_users SET status = "Active" WHERE id = ?', [
appUserId,
]);
res.status(200).json({ message: "ID approved successfully" });
}
} catch (error) {
console.error("Error approving ID:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.getAppUsers = async (req, res) => {
try {
const { role } = req.user;
// Ensure the user has the proper role
if (!["Superadmin", "Admin", 9, 8].includes(role)) {
return res.status(403).json({ error: "Unauthorized to view app users" });
}
// Query to fetch app users
const query = `
SELECT * FROM app_users;
`;
const users = await db.query(query);
if (users.length === 0) {
return res
.status(404)
.json({ message: "No app users found for this hospital" });
}
res.status(200).json({
message: "App users fetched successfully",
data: users,
});
} catch (error) {
console.error("Error fetching app users:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.getAppUserByHospitalId = async (req, res) => {
try {
const { role } = req.user;
const { id } = req.params; // Extract user ID from request parameters
// Log authenticated user details
// Ensure the user has the proper role
if (!["Superadmin", "Admin", 8, 9].includes(role)) {
return res.status(403).json({ error: "Unauthorized to view app users" });
}
// fetching hospital code from hospital id
const query1 = `SELECT * FROM hospitals WHERE id = ?`;
const result1 = await db.query(query1, [id]);
const hospital_code = result1[0].hospital_code;
console.log("result1----", result1);
// Query to fetch the app user by hospital_code
const query = `SELECT * FROM app_users WHERE hospital_code = ?`;
const users = await db.query(query, [hospital_code]);
if (users.length === 0) {
return res.status(404).json({ message: "App users not found" });
}
res.status(200).json({
message: "App user fetched successfully",
data: users, // Return single user object
});
} catch (error) {
console.error("Error fetching app user:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.deleteAppUser = async (req, res) => {
try {
const { id } = req.params;
const { hospital_code } = req.user;
// Check if the user exists
const appUserquery = "SELECT * FROM app_users WHERE id = ?";
const users = await db.query(appUserquery, [id]);
if (users.length === 0) {
return res
.status(404)
.json({ message: "No app users found for this hospital" });
}
// Ensure only Admin or Superadmin can delete users
if (!["Admin", "Superadmin", 8, 7,"AppUser"].includes(req.user.role)) {
return res.status(403).json({ error: "Unauthorized to delete users" });
}
// Ensure user belongs to the same hospital
const query =
"SELECT hospital_code,id_photo_url FROM app_users WHERE id = ? AND hospital_code = ?";
const result = await db.query(query, [id, hospital_code]);
if (!result || result.length === 0) {
return res
.status(403)
.json({ error: "User does not belong to your hospital" });
}
// Unlink (delete) the associated file if it exists and is not null
if (result[0].id_photo_url) {
const filePath = path.join(__dirname, '..','..', 'uploads', result[0].id_photo_url.replace(/^\/uploads\//, ''));
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
console.error(`File not found: ${filePath}`);
return;
}
fs.unlink(filePath, (err) => {
if (err) {
console.error('Error deleting file:', err.message);
} else {
console.log('File deleted successfully:', filePath);
}
});
});
} else {
console.log('No file to delete - id_photo_url is null');
}
// delete feedback
await db.query(
'DELETE FROM feedback WHERE sender_type = ? AND sender_id = ?',
['appuser', id] // Fixed: should be 'id' not 'req.user.id'
);
// Delete the user
const deleteResult = await db.query("DELETE FROM app_users WHERE id = ?", [
id,
]);
if (deleteResult.affectedRows === 0) {
return res.status(404).json({ error: "User not found" });
}
res.status(200).json({ message: "User deleted successfully" });
} catch (error) {
console.error("Error deleting user:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
// query title CRUD
exports.updateQueryTitle = async (req, res) => {
const id = req.user.id;
const { query_title } = req.body;
if (!query_title) {
return res.status(400).json({ error: "query_title is required" });
}
try {
const result = await db.query(
"UPDATE app_users SET query_title = ? WHERE id = ?",
[query_title, id]
);
if (result.affectedRows === 0) {
return res.status(404).json({ error: "User not found" });
}
res.json({ message: "Query title updated successfully" });
} catch (error) {
console.error("Error updating query title:", error);
res.status(500).json({ error: "Internal server error" });
}
};
// Delete query_title for a user by user ID
exports.getShortTitle = (req, res) => {
const { question } = req.body;
console.log("Question: ", question);
if (!question) return res.status(400).json({ error: "No question provided" });
let shortTitle = nlp(question).sentences().toTitleCase().out("text");
// Remove unnecessary question words
shortTitle = shortTitle
.replace(
/^(What|How|Why|When|Where|Which|Who|Is|Are|Do|Does|Can|Could)\s+/i,
""
)
.trim();
if (!shortTitle)
return res.status(400).json({ error: "Invalid question format" });
console.log("short question:", shortTitle);
res.json({ query_title: shortTitle });
};
exports.deleteQueryTitle = async (req, res) => {
const id = req.user.id;
try {
const result = await db.query(
"UPDATE app_users SET query_title = NULL WHERE id = ?",
[id]
);
if (result.affectedRows === 0) {
return res.status(404).json({ error: "User not found" });
}
res.json({ message: "Query title deleted successfully" });
} catch (error) {
console.error("Error deleting query title:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.sendOtp = async (req, res) => {
try {
const { email } = req.body;
// Validate email input
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
// Check if user exists
const user = await db.query(
"SELECT id, username, hospital_code FROM app_users WHERE email = ?",
[email]
);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
console.log("user", user);
const userId = user[0].id;
const hospital_code = user[0].hospital_code;
// fetch hospital name of app_user
const hospital = await db.query(
"SELECT name_hospital FROM hospitals WHERE hospital_code = ?",
[hospital_code]
);
console.log("hospital result : ", hospital[0].name_hospital);
// Generate a 6-digit OTP
const otp = Math.floor(100000 + Math.random() * 900000).toString();
const expiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000); // OTP expires in 1 hour
console.log("otp : ", otp, "expiresAt: ", expiresAt);
// Store OTP in database
await db.query(
"UPDATE app_users SET otp_code = ?, otp_expires_at = ? WHERE id = ?",
[otp, expiresAt, userId]
);
// // Send OTP via email
const info = await sendMail(
email,
hospital[0].name_hospital,
user[0].username,
otp
);
res.json({ message: "OTP sent successfully", email_status: info.response });
} catch (error) {
console.error("Error sending OTP:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.changePassword = async (req, res) => {
try {
const { email, otp, new_password } = req.body;
console.log("email : ", email);
// Validate inputs
if (!email || !otp || !new_password) {
return res
.status(400)
.json({ error: "Email, OTP, and new password are required" });
}
// Fetch OTP from the database
const user = await db.query(
"SELECT id, otp_code, otp_expires_at FROM app_users WHERE email = ?",
[email]
);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
const userData = user[0];
// ✅ Check if OTP matches
if (userData.otp_code !== otp) {
return res.status(400).json({ error: "Invalid OTP" });
}
// ✅ Check if OTP is expired
if (new Date() > new Date(userData.otp_expires_at)) {
return res.status(400).json({ error: "OTP expired. Request a new one." });
}
// ✅ Hash the new password
const hashedPassword = await bcrypt.hash(new_password, 10);
// ✅ Update password in DB & clear OTP
await db.query(
"UPDATE app_users SET hash_password = ?, otp_code = NULL, otp_expires_at = NULL WHERE id = ?",
[hashedPassword, userData.id]
);
res.json({ message: "Password changed successfully!" });
} catch (error) {
console.error("Error resetting password:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.sendPinOtp = async (req, res) => {
try {
const { email } = req.body;
// Validate email input
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
// Check if user exists
const user = await db.query(
"SELECT id, username, hospital_code FROM app_users WHERE email = ?",
[email]
);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
console.log("user", user);
const userId = user[0].id;
const hospital_code = user[0].hospital_code;
// fetch hospital name of app_user
const hospital = await db.query(
"SELECT name_hospital FROM hospitals WHERE hospital_code = ?",
[hospital_code]
);
console.log("hospital result : ", hospital[0].name_hospital);
// Generate a 6-digit OTP
const otp = Math.floor(100000 + Math.random() * 900000).toString();
const expiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000); // OTP expires in 1 hour
console.log("otp : ", otp, "expiresAt: ", expiresAt);
// Store OTP in database
await db.query(
"UPDATE app_users SET pin_otp = ?, pin_otp_expiry = ? WHERE id = ?",
[otp, expiresAt, userId]
);
// // Send OTP via email
const info = await sendMail(
email,
hospital[0].name_hospital,
user[0].username,
otp
);
res.json({ message: "OTP sent successfully", email_status: info.response });
} catch (error) {
console.error("Error sending OTP:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.changePinByOtp = async (req, res) => {
try {
const { email, otp, new_pin } = req.body;
console.log("email : ", email);
// Validate inputs
if (!email || !otp || !new_pin) {
return res
.status(400)
.json({ error: "Email, OTP, and new pin are required" });
}
// Fetch OTP from the database
const user = await db.query(
"SELECT id, pin_otp, pin_otp_expiry FROM app_users WHERE email = ?",
[email]
);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
const userData = user[0];
// ✅ Check if OTP matches
if (userData.pin_otp !== otp) {
return res.status(400).json({ error: "Invalid OTP" });
}
// ✅ Check if OTP is expired
if (new Date() > new Date(userData.otp_expires_at)) {
return res.status(400).json({ error: "OTP expired. Request a new one." });
}
// ✅ Hash the new password
// ✅ Update password in DB & clear OTP
await db.query(
"UPDATE app_users SET pin_number = ?, pin_otp = NULL, pin_otp_expiry = NULL WHERE id = ?",
[new_pin, userData.id]
);
res.json({ message: "Pin changed successfully!" });
} catch (error) {
console.error("Error resetting pin:", error);
res.status(500).json({ error: "Internal server error" });
}
};
async function sendMail(email, hospital_name, username, otp) {
const mailOptions = {
from: "kavya.j@tech4biz.io", // Sender's email
to: email, // Recipient's email
subject: "Spurrinai Login Credentials", // Email subject
html: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Your Password or Pin - Spurrinai Medical Platform</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Syne:wght@400..800&display=swap');
body {
font-family: 'Inter', sans-serif;
margin: 0;
padding: 0;
background-color: #ebf3fa;
color: #333;
}
.email-container {
max-width: 600px;
margin: 20px auto;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
}
.email-header {
background: linear-gradient(135deg, #2193b0, #6dd5ed);
padding: 30px;
text-align: center;
color: white;
}
.hospital-name {
display: inline-block;
background-color: rgba(255, 255, 255, 0.2);
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
margin-bottom: 10px;
}
.email-header h1 {
margin: 10px 0 0;
font-size: 26px;
font-weight: 500;
}
.email-content {
padding: 40px 30px;
}
.greeting {
font-size: 30px;
font-weight: 700;
margin-bottom: 20px;
color: #303030;
}
.greeting-text{
font-size: 18px;
font-weight: 400;
margin-bottom: 20px;
line-height: 1.7;
color: #303030;
}
.verification-code {
background-color: #f5f9fc;
border: 1px solid #e0e9f0;
border-radius: 8px;
padding: 20px;
margin: 25px 0;
text-align: center;
}
.code {
font-family: 'Courier New', monospace;
font-size: 32px;
letter-spacing: 6px;
color: #2193b0;
font-weight: bold;
padding: 10px 0;
}
.reset-button {
display: block;
background-color: #2193b0;
color: white;
text-decoration: none;
text-align: center;
padding: 15px 20px;
border-radius: 5px;
margin: 30px auto;
max-width: 250px;
font-weight: 500;
transition: background-color 0.3s;
}
.reset-button:hover {
background-color: #1a7b92;
}
.expiry-note {
background-color: #fff8e1;
border-left: 4px solid #ffc107;
padding: 12px 15px;
margin: 25px 0;
font-size: 14px;
color: #856404;
}
.security-note {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 14px;
color: #666;
}
.email-footer {
background-color: #f5f9fc;
padding: 20px;
text-align: center;
font-size: 13px;
color: #888;
border-top: 1px solid #e0e9f0;
}
.support-link {
color: #2193b0;
text-decoration: none;
}
.device-info {
margin-top: 20px;
background-color: #f5f9fc;
padding: 15px;
border-radius: 6px;
font-size: 14px;
}
.device-info p {
margin: 5px 0;
color: #666;
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-header">
<div class="hospital-name"> ${hospital_name}</div>
<h1>Reset Your Password or Pin</h1>
</div>
<div class="email-content">
<div class="greeting">Hello ${username},</div>
<p class="greeting-text" >We received a request to <strong>reset the password</strong> for your account on the <strong> Spurrinai healthcare platform</strong>. For your security reasons, please verify this action.</p>
<div class="verification-code">
<p>Please enter this verification code:</p>
<div class="code">${otp}</div>
<p>on the password reset screen</p>
</div>
<a href="spurrinai://ResetPassword/${otp}" class="reset-button">Reset Password</a>
<div class="expiry-note">
<strong>Note:</strong> This verification code will expire in 2 hours for security reasons.
</div>
<div class="security-note">
<p>If you did not request this password reset, please contact our IT security team immediately at <a href="mailto:info@spurrinai.com" class="support-link">info@spurrinai.com</a> or call our support line at +1 (800) 555-1234.</p>
</div>
</div>
<div class="email-footer">
<p>© 2025 Spurrinai - Healthcare Data Management Platform</p>
<p>This is an automated message. Please do not reply to this email.</p>
<p>Need help? Contact <a href="mailto:support@spurrinai.com" class="support-link">support@spurrinai.com</a></p>
</div>
</div>
</body>
</html>`,
};
try {
const info = await transporter.sendMail(mailOptions);
console.log("info: ", info);
return info;
} catch (error) {
console.error(`Error sending email to ${email}:`, error);
return error;
}
}
// chat-sessions
exports.getChatSessionsByAppUserID = async (req, res) => {
try {
console.log("user data", req.user)
const { role } = req.user;
// Ensure the user has the proper role
// if (!['Superadmin', 'Admin', 9, 8].includes(role)) {
// return res.status(403).json({ error: 'Unauthorized to view chats' });
// }
if (!req.user.id) {
return res.status(400).json({ error: "user id not found" });
}
// Query to fetch app users
const query = `
SELECT session_id,
MIN(session_title) AS session_title,
MIN(created_at) AS created_at
FROM interaction_logs
WHERE app_user_id = ${req.user.id}
AND session_id IS NOT NULL
GROUP BY session_id
ORDER BY session_id DESC;
`;
const session_ids = await db.query(query);
if (session_ids.length === 0) {
return res.status(404).json({ message: 'No interaction logs found for this app user', data: session_ids });
}
res.status(200).json({
message: 'Interaction logs fetched successfully',
data: session_ids,
});
} catch (error) {
console.error('Error fetching interaction logs:', error.message);
res.status(500).json({ error: 'Internal server error' });
}
}
exports.getChatForEachSession = async (req, res) => {
try {
console.log("user data", req.user)
const session_id = req.params.session_id;
console.log("session_id ", session_id)
// Ensure the user has the proper role
// if (!['Superadmin', 'Admin', 9, 8].includes(role)) {
// return res.status(403).json({ error: 'Unauthorized to view chats' });
// }
if (!req.user.id) {
return res.status(400).json({ error: "user id not found" });
}
// Query to fetch app users
const query = `
SELECT * FROM interaction_logs
WHERE app_user_id = ?
AND session_id = ?
AND status = 'Active';
`;
const values = [req.user.id, session_id];
const interaction_logs = await db.query(query, values);
if (interaction_logs.length === 0) {
return res.status(404).json({ message: 'No interaction logs found for this app user or session', data: interaction_logs });
}
res.status(200).json({
message: 'Interaction logs fetched successfully',
data: interaction_logs,
});
} catch (error) {
console.error('Error fetching interaction logs:', error.message);
res.status(500).json({ error: 'Internal server error' });
}
}
exports.deleteChatSessions = async (req, res) => {
const id = req.user.id;
const { session_id } = req.body
console.log("session_id ", session_id)
console.log("app_user_id ", id)
try {
const result = await db.query(
"UPDATE interaction_logs SET session_id = NULL WHERE app_user_id = ? AND session_id = ?",
[id, session_id]
);
if (result.affectedRows === 0) {
return res.status(404).json({ error: "No session found" });
}
res.json({ message: "Chat session deleted successfully" });
} catch (error) {
console.error("Error deleting query title:", error);
res.status(500).json({ error: "Internal server error" });
}
}
exports.clearChatbasedOnSessions = async (req, res) => {
const id = req.user.id;
const { session_id } = req.body
try {
const result = await db.query(
"UPDATE interaction_logs SET status = ? WHERE app_user_id = ? AND session_id = ?",
['Inactive', id, session_id]
);
if (result.affectedRows === 0) {
return res.status(404).json({ error: "No chat found" });
}
res.json({ message: "Chat deleted successfully" });
} catch (error) {
console.error("Error deleting chat:", error);
res.status(500).json({ error: "Internal server error" });
}
}
exports.getChatByTime = async (req, res) => {
try {
console.log("user data", req.user)
const { lastcreated_at } = req.body;
// Ensure the user has the proper role
// if (!['Superadmin', 'Admin', 9, 8].includes(role)) {
// return res.status(403).json({ error: 'Unauthorized to view chats' });
// }
if (!req.user.id) {
return res.status(400).json({ error: "user id not found" });
}
// Query to fetch app users
let results
console.log("last created at", lastcreated_at)
if (!lastcreated_at) { // Handles null, undefined, empty string, etc.
const query = `
SELECT * FROM interaction_logs
WHERE app_user_id = ?;
`;
results = await db.query(query, [req.user.id]);
} else {
console.log("Executing with last_created_at:", lastcreated_at);
const query = `
SELECT * FROM interaction_logs
WHERE app_user_id = ?
AND created_at > ?;
`;
results = await db.query(query, [req.user.id, lastcreated_at]);
}
if (results.length === 0) {
return res.status(404).json({ message: 'No interaction logs found for this app user' });
}
res.status(200).json({
message: 'Interaction logs fetched successfully',
data: results,
});
} catch (error) {
console.error('Error fetching interaction logs:', error.message);
res.status(500).json({ error: 'Internal server error' });
}
}
exports.checkEmailCode = async (req, res) => {
try {
const { email, hospital_code } = req.body;
// Check if the email and code are provided
if (!email || !hospital_code) {
return res.status(400).json({ error: "Email and code are required" });
}
console.log("received data", email, hospital_code)
// Check if the email exists in the database
const useremail = await db.query("SELECT * FROM app_users WHERE email = ?", [email]);
console.log("user email", useremail)
if (useremail.length) {
return res.status(404).json({ error: "Email already in use" });
}
// Check if the code is correct
const usercode = await db.query("SELECT hospital_code FROM hospital_users WHERE hospital_code = ?", [hospital_code]);
console.log("user code", usercode)
if (!usercode.length) {
return res.status(404).json({ error: "Please enter valid hospital code" });
}
// Code is correct, return success response
res.status(200).json({ message: "Email code is correct" });
} catch (error) {
console.error("Error checking email code:", error);
res.status(500).json({ error: "Internal server error" });
}
}
// get popular topics
exports.getPopularTopics = async (req, res) => {
let popularTopics = [];
try {
// Fetch top 4 most viewed questions
console.log("req.user data : ", req.user)
const rows = await getMappedPopularQuestionsAnswers(req.user.hospital_code);
if (!rows) {
popularTopics = []
}
else {
popularTopics = rows;
}
console.log('hospital data : ', rows)
res.status(200).json({
success: true,
data: popularTopics
});
} catch (error) {
console.error('Error fetching popular topics:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
}
const getMappedPopularQuestionsAnswers = async (hospitalCode) => {
console.log("Hospital code is---", hospitalCode);
try {
const query = `
WITH question_frequency AS (
SELECT
query,
response,
COUNT(*) as frequency
FROM interaction_logs
WHERE hospital_code = ?
GROUP BY query, response
HAVING COUNT(*) > 1
ORDER BY frequency DESC
LIMIT 10
),
recent_questions AS (
SELECT
il.*,
COALESCE(qf.frequency, 1) as frequency
FROM interaction_logs il
LEFT JOIN question_frequency qf
ON il.query = qf.query
AND il.response = qf.response
WHERE il.hospital_code = ?
ORDER BY
COALESCE(qf.frequency, 0) DESC,
il.created_at DESC
LIMIT 10
)
SELECT * FROM recent_questions;
`;
const rows = await db.query(query, [hospitalCode, hospitalCode]);
console.log("Fetched questions before filtering:", rows);
// If no data found, return message
if (!rows || rows.length === 0) {
return { message: "No interactions made in your hospital" };
}
// Skip the row if either condition is true
const filteredRows = rows.filter(row =>
!(row.query.toLowerCase().includes("yes") || row.response.includes("Please reply with 'yes'"))
);
// If no data after filtering, return message
if (filteredRows.length === 0) {
return { message: "No interactions made in your hospital" };
}
// Return only the top 4 filtered results
return filteredRows.slice(0, 4);
} catch (error) {
console.error("Error fetching popular topics:", error.message);
throw new Error("Internal server error");
}
};
exports.changePin = async (req, res) => {
try {
const { current_pin, new_pin } = req.body;
const userId = req.user.id;
if (!current_pin || !new_pin) {
return res.status(400).json({ error: "Current pin and new pin are required" });
}
// Add PIN length validation
if (new_pin.length !== 4 || !/^\d+$/.test(new_pin)) {
return res.status(400).json({ error: "New PIN must be exactly 4 digits" });
}
// Get user's current pin
const userQuery = "SELECT pin_number FROM app_users WHERE id = ?";
const user = await db.query(userQuery, [userId]);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
// Verify current pin
if (user[0].pin_number !== current_pin) {
return res.status(400).json({ error: "Current pin is incorrect" });
}
// Check if new pin is same as current pin
if (current_pin === new_pin) {
return res.status(400).json({ error: "New PIN cannot be the same as current PIN" });
}
// Update to new pin
await db.query(
"UPDATE app_users SET pin_number = ? WHERE id = ?",
[new_pin, userId]
);
res.json({ message: "Pin changed successfully" });
} catch (error) {
console.error("Error changing pin:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.forgotPin = async (req, res) => {
try {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
// Get user details
const userQuery = `
SELECT au.id, au.username, au.pin_number, au.hospital_code, h.name_hospital
FROM app_users au
JOIN hospitals h ON au.hospital_code = h.hospital_code
WHERE au.email = ?
`;
const user = await db.query(userQuery, [email]);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
const userData = user[0];
// Send pin via email
const mailOptions = {
from: "kavya.j@tech4biz.io",
to: email,
subject: "Your Spurrinai PIN",
html: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your PIN - Spurrinai Medical Platform</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Syne:wght@400..800&display=swap');
body {
font-family: 'Inter', sans-serif;
margin: 0;
padding: 0;
background-color: #ebf3fa;
color: #333;
}
.email-container {
max-width: 600px;
margin: 20px auto;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
}
.email-header {
background: linear-gradient(135deg, #2193b0, #6dd5ed);
padding: 30px;
text-align: center;
color: white;
}
.hospital-name {
display: inline-block;
background-color: rgba(255, 255, 255, 0.2);
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
margin-bottom: 10px;
}
.email-content {
padding: 40px 30px;
}
.greeting {
font-size: 30px;
font-weight: 700;
margin-bottom: 20px;
color: #303030;
}
.pin-display {
background-color: #f5f9fc;
border: 1px solid #e0e9f0;
border-radius: 8px;
padding: 20px;
margin: 25px 0;
text-align: center;
}
.pin {
font-family: 'Courier New', monospace;
font-size: 32px;
letter-spacing: 6px;
color: #2193b0;
font-weight: bold;
padding: 10px 0;
}
.security-note {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 14px;
color: #666;
}
.email-footer {
background-color: #f5f9fc;
padding: 20px;
text-align: center;
font-size: 13px;
color: #888;
border-top: 1px solid #e0e9f0;
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-header">
<div class="hospital-name">${userData.name_hospital}</div>
<h1>Your PIN Information</h1>
</div>
<div class="email-content">
<div class="greeting">Hello ${userData.username},</div>
<p>Here is your PIN for the Spurrinai healthcare platform:</p>
<div class="pin-display">
<div class="pin">${userData.pin_number}</div>
</div>
<div class="security-note">
<p>For security reasons, please do not share this PIN with anyone. If you did not request this information, please contact our support team immediately.</p>
</div>
</div>
<div class="email-footer">
<p>© 2025 Spurrinai - Healthcare Data Management Platform</p>
<p>This is an automated message. Please do not reply to this email.</p>
<p>Need help? Contact <a href="mailto:support@spurrinai.com">support@spurrinai.com</a></p>
</div>
</div>
</body>
</html>`
};
await transporter.sendMail(mailOptions);
res.json({ message: "PIN has been sent to your email" });
} catch (error) {
console.error("Error in forgot pin:", error);
res.status(500).json({ error: "Internal server error" });
}
};