spurrin-backend/src/controllers/hospitalController.js
2025-06-09 19:09:33 +05:30

1687 lines
51 KiB
JavaScript

const db = require("../config/database");
const hospitalService = require("../services/hospitalService");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const nodemailer = require("nodemailer");
const path = require("path");
const sender_mail = process.env.mail;
const back_url = process.env.BACK_URL;
const fs = require("fs");
const tokenService = require('../services/tokenService');
const transporter = nodemailer.createTransport({
host: "smtp.zoho.com", // Zoho SMTP Server
port: 465, // Use 465 for SSL or 587 for TLS
secure: true, // Set to true for port 465, false for port 587
auth: {
user: "kavya.j@tech4biz.io", // Your Zoho email address
pass: "8pQfkBw8gbrz", // Your Zoho App Password (not your account password)
}
// tls: {
// minVersion: "TLSv1.2",
// ciphers: "SSLv3",
// },
});
function generateHospitalCode() {
const length = 12;
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let code = "";
for (let i = 0; i < length; i++) {
code += characters.charAt(Math.floor(Math.random() * characters.length));
}
return code;
}
exports.createHospital = async (req, res) => {
try {
const {
name_hospital,
subdomain,
primary_admin_email,
primary_admin_password,
primary_color,
secondary_color,
logo_url,
admin_name,
mobile_number,
location,
super_admin_id, // This comes from request body
} = req.body;
// ✅ Step 1: Ensure the logged-in SuperAdmin is the one creating the hospital
if (req.user.id !== super_admin_id) {
return res.status(403).json({
error:
"Unauthorized: You cannot create a hospital for another SuperAdmin",
});
}
// check email if already exists
const spurrinEmailQuery =
"SELECT email from super_admins WHERE email = ?";
const spurrinEmailResult = await db.query(spurrinEmailQuery, [primary_admin_email]);
if (spurrinEmailResult.length > 0) {
return res.status(403).json({ error: "Email already exists!" });
}
const hsptUsrEmailQuery =
"SELECT email from hospital_users WHERE email = ?";
const hsptUsrEmailResult = await db.query(hsptUsrEmailQuery, [primary_admin_email]);
if (hsptUsrEmailResult.length > 0) {
return res.status(403).json({ error: "Email already exists!" });
}
// ✅ Step 2: Generate a unique hospital code
let hospitalCode;
let isUnique = false;
while (!isUnique) {
hospitalCode = generateHospitalCode();
const codeExists = await db.query(
"SELECT COUNT(*) as count FROM hospitals WHERE hospital_code = ?",
[hospitalCode]
);
if (codeExists[0].count === 0) {
isUnique = true;
}
}
// ✅ Step 3: Validate if the SuperAdmin exists in the database
const superAdminQuery =
"SELECT id, access_token FROM super_admins WHERE id = ?";
const superAdminResult = await db.query(superAdminQuery, [super_admin_id]);
if (superAdminResult.length === 0) {
return res.status(400).json({ error: "Invalid super_admin_id" });
}
const superAdmin = superAdminResult[0];
// ✅ Step 4: Ensure the access token used in the request matches the one stored in the database
if (superAdmin.access_token !== req.headers.authorization.split(" ")[1]) {
return res.status(403).json({
error:
"Unauthorized: Access token does not match the SuperAdmin's token in the database",
});
}
// ✅ Step 5: Hash the primary admin's password
const hashedPassword = await bcrypt.hash(primary_admin_password, 10);
// ✅ Step 6: Send an email to notify
const mailOptions = {
from: 'kavya.j@tech4biz.io', // Sender's email
to: primary_admin_email, // Recipient's email
subject: "Spurrinai Login Credentials", // Email subject
html: `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome Email</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');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Inter, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 700px;
margin: 0 auto;
background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.header {
background-color: #B4EAE4;
padding: 20px;
position: relative;
height: fit-content;
display: flex;
justify-content: space-between;
align-items: flex-start;
height: 250px;
}
.logo {
font-size: 24px;
font-weight: bold;
color: #333;
}
.header-image {
height: 250px;
width: auto;
}
.content {
padding: 24px;
}
.greeting {
font-size: 24px;
margin-bottom: 16px;
color: #333;
font-weight: bold;
}
.welcome-text {
margin-bottom: 24px;
line-height: 1.6;
}
.credentials-title {
margin-bottom: 16px;
font-weight: 500;
}
.credentials-grid {
margin-bottom: 24px;
}
.credential-row {
display: flex;
border: 1px solid #e0e0e0;
margin-bottom: -1px;
}
.credential-label {
flex: 0 0 140px;
padding: 12px;
background-color: #F1fffe;
font-weight: 500;
border-right: 1px solid #e0e0e0;
}
.credential-value {
flex: 1;
padding: 12px;
word-break: break-word;
}
.security-note {
font-style: italic;
color: #666;
margin: 24px 0;
}
.button {
display: inline-block;
background: #333;
color: #fff;
padding: 12px 24px;
text-decoration: none;
border-radius: 4px;
font-weight: 500;
}
@media screen and (min-width:400px) and (max-width: 768px) {
.container {
margin: 20px auto;
width: 90%;
}
.content {
padding: 20px;
}
.header {
padding: 15px;
background-color: #b4eae4;
padding: 20px;
position: relative;
height: fit-content;
display: flex;
justify-content: start;
align-items: start;
border-radius: 12px 12px 0px 0px;
flex-direction: column;
height: 250px;
}
.header-image {
height: 250px;
width: 100%;
}
}
@media screen and (max-width: 400px) {
.header {
padding: 15px;
background-color: #b4eae4;
padding: 20px;
position: relative;
height: fit-content;
display: flex;
justify-content: start;
align-items: start;
border-radius: 12px 12px 0px 0px;
flex-direction: column;
}
.header-image {
height: 250px;
width: 100%;
}
.container {
margin: 20px auto;
width: 90%;
}
.content {
padding: 16px;
}
.credential-row {
flex-direction: column;
margin-bottom: 16px;
border: none;
}
.credential-label {
flex: none;
border: 1px solid #e0e0e0;
border-bottom: none;
border-radius: 4px 4px 0 0;
}
.credential-value {
flex: none;
border: 1px solid #e0e0e0;
border-radius: 0 0 4px 4px;
background: #fff;
}
.greeting {
font-size: 20px;
}
.button {
display: block;
text-align: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header"
style="background-image: url(${back_url}'public/images/email-banner.png');"
>
<div class="logo">Spurrinai</div>
<!-- <img src="/SpurrinAI/public/images/email-banner.png" alt="Doctor illustration" class="header-image"> -->
</div>
<div class="content">
<h1 class="greeting">Greetings, ${admin_name},</h1>
<div class="welcome-text">
Congratulations! Your hospital, <strong>${name_hospital}</strong>, has been successfully onboarded to
<strong>Spurrinai</strong>. We are excited to have you on board and look forward to supporting your
hospital's needs.
</div>
<div class="credentials-title">
Please find your hospital's login credentials below to access the platform:
</div>
<div class="credentials-grid">
<div class="credential-row">
<div class="credential-label">Hospital Name</div>
<div class="credential-value">${name_hospital}</div>
</div>
<div class="credential-row">
<div class="credential-label">Domain</div>
<div class="credential-value">${subdomain}</div>
</div>
<div class="credential-row">
<div class="credential-label">Username</div>
<div class="credential-value">${primary_admin_email}</div>
</div>
<div class="credential-row">
<div class="credential-label">Temporary Password</div>
<div class="credential-value">${primary_admin_password}</div>
</div>
</div>
<div class="security-note">
For your security, we recommend changing your password immediately after logging in.
</div>
<a href="https://${subdomain}superadmin" class="button">Log In and Change Password</a>
</div>
</div>
</body>
</html>`,
};
const insertHospitalQuery = `
INSERT INTO hospitals (
name_hospital,
subdomain,
primary_admin_email,
primary_admin_password,
primary_color,
secondary_color,
logo_url,
status,
onboarding_status,
admin_name,
mobile_number,
location,
super_admin_id,
hospital_code,
type
) VALUES (?, ?, ?, ?, ?, ?, ?, 'Active', 'Pending', ?, ?, ?, ?,?,NULL)
`;
const hospitalResult = await db.query(insertHospitalQuery, [
name_hospital,
subdomain,
primary_admin_email,
hashedPassword,
primary_color,
secondary_color,
logo_url,
admin_name,
mobile_number,
location,
super_admin_id,
hospitalCode,
]);
// ✅ Step 7: Get the new hospital ID
const hospitalId = hospitalResult.insertId;
// ✅ Step 8: Generate a refresh token for the hospital's primary admin
// Step 1: Insert the primary admin into the hospital_users table
const insertUserQuery = `
INSERT INTO hospital_users (
hospital_id,
email,
hash_password,
role_id,
is_default_admin,
requires_onboarding,
password_reset_required,
phone_number,
status,
hospital_code,
name,
type,
location
) VALUES (?, ?, ?, ?, TRUE, TRUE, TRUE, ?, 'Active', ?, ?, NULL,?)
`;
// Assuming 7 is the role ID for Superadmin
const roleId = 7;
const insertResult = await db.query(insertUserQuery, [
hospitalId,
primary_admin_email,
hashedPassword,
roleId,
mobile_number,
hospitalCode,
admin_name,
location
]);
console.log("insert result", insertResult);
// Step 2: Retrieve the inserted user ID
const insertedUserId = insertResult.insertId;
const payload = { id: insertedUserId, email: primary_admin_email, role: roleId };
const accessToken = tokenService.generateAccessToken(payload);
const expiryTimestamp = new Date();
expiryTimestamp.setHours(expiryTimestamp.getHours() + 5); // Add 5 hours
// Step 3: Generate the refresh token
const refreshTokenPayload = {
id: insertedUserId,
email: primary_admin_email,
role: "Superadmin",
};
const refreshToken = jwt.sign(
refreshTokenPayload,
process.env.JWT_REFRESH_TOKEN_SECRET
);
// Step 4: Update the refresh token in the database
const updateRefreshTokenQuery = `UPDATE hospital_users SET refresh_token = ?, access_token = ?, access_token_expiry= ? WHERE id = ?`;
await db.query(updateRefreshTokenQuery, [refreshToken, accessToken, expiryTimestamp, insertedUserId]);
if (!primary_admin_email) {
return res.status(400).json({ error: "Recipient email is required" });
}
const responseData = {
message: "Hospital and Primary SuperAdmin created successfully!",
hospital: {
id: hospitalId,
name_hospital,
subdomain,
primary_admin_email,
primary_color,
secondary_color,
logo_url,
admin_name,
mobile_number,
location,
super_admin_id,
hospitalCode
},
refreshToken,
};
// 3. Send email in a separate try-catch
try {
const info = await transporter.sendMail(mailOptions);
responseData.emailInfo = info.response;
} catch (emailError) {
console.error("Email sending failed:", emailError.message);
responseData.emailInfo = "Email sending failed: " + emailError.message;
}
// 4. Send final response
res.status(201).json(responseData);
} catch (error) {
console.error("Error creating hospital and admin:", error.message);
res.status(500).json({ error: error.message });
}
};
exports.uploadLogo = (req, res) => {
try {
const logoUrl = req.file ? req.file.path : null;
res
.status(200)
.json({ message: "Logo uploaded successfully!", logo_url: logoUrl });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
exports.getHospitalList = async (req, res) => {
try {
// Ensure we are filtering by the authenticated Spurrinadmin
const superAdminId = req.user.id; // Extract authenticated Spurrinadmin ID
const query = "SELECT * FROM hospitals";
const hospitals = await db.query(query);
res.status(200).json({
message: "Hospital list fetched successfully!",
data: hospitals,
});
} catch (error) {
console.error("Error fetching hospital list:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.getHospitalById = async (req, res) => {
try {
const { id } = req.params; // Extract the hospital ID from route parameters
// Query the database to fetch the hospital by ID
const query = "SELECT * FROM hospitals WHERE id = ?";
const result = await db.query(query, [id]);
// Check if the hospital exists
if (result.length === 0) {
return res.status(404).json({ error: "Hospital not found" });
}
res.status(200).json({
message: "Hospital fetched successfully!",
data: result[0], // Return the first (and only) result
});
} catch (error) {
console.error("Error fetching hospital by ID:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
exports.updateHospital = async (req, res) => {
try {
const { id } = req.params; // Get the hospital ID from the route parameter
const updateData = req.body; // Get the fields to update from the request body
// Fetch the hospital to validate ownership
const hospitalQuery = "SELECT id hospital_code, super_admin_id FROM hospitals WHERE id = ?";
const hospitalResult = await db.query(hospitalQuery, [id]);
const hospitalQueryUsr = "SELECT id FROM hospital_users WHERE hospital_id = ?";
const hospitalResultUsr = await db.query(hospitalQueryUsr, [id]);
// console.log("requested user", req.user);
if (hospitalResult.length === 0) {
return res.status(404).json({ error: "Hospital not found" });
}
console.log('hspt id----', req.user.id)
console.log('db hspt id', hospitalResultUsr[0])
if (req.user.id !== hospitalResultUsr[0].id && req.user.id !== hospitalResult[0].super_admin_id) {
return res
.status(403)
.json({ error: "You can only edit the hospital you have created" });
}
const validColumns = new Set([
"name_hospital",
"primary_admin_password",
"primary_color",
"secondary_color",
"logo_url",
"status",
"onboarding_status",
"admin_name",
"mobile_number",
"location",
"super_admin_id",
]);
// Ensure no extra fields are present
for (const key of Object.keys(updateData)) {
if (!validColumns.has(key)) {
return res.status(400).json({ error: `Invalid field or cannot update: ${key}` });
}
}
// Build the SQL SET clause dynamically
const fields = [];
const values = [];
for (const [key, value] of Object.entries(updateData)) {
fields.push(`${key} = ?`);
values.push(value);
}
values.push(id); // Add the hospital ID to the values array for the WHERE clause
// Construct the SQL query
const query = `UPDATE hospitals SET ${fields.join(", ")} WHERE id = ?`;
const result = await db.query(query, values);
const queryhspt = `SELECT * FROM hospitals WHERE id = ?`;
const resulthspt = await db.query(queryhspt, [id]);
// Check if any row was updated
if (result.affectedRows === 0) {
return res
.status(404)
.json({ error: "Hospital not found or no changes made" });
}
res
.status(200)
.json({ message: "Hospital updated successfully!", data: resulthspt });
} catch (error) {
console.error("Error updating hospital:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
// exports.updateHospital = async (req, res) => {
// try {
// const { id } = req.params; // Get the hospital ID from the route parameter
// const updateData = req.body; // Get the fields to update from the request body
// // Fetch the hospital to validate ownership
// // const hospitalQuery = "SELECT hospital_code, super_admin_id FROM hospitals WHERE id = ?";
// // const hospitalResult = await db.query(hospitalQuery, [id]);
// // console.log("requested user", req.user);
// // if (hospitalResult.length === 0) {
// // return res.status(404).json({ error: "Hospital not found" });
// // }
// // if (req.user.id !== hospitalResult[0].super_admin_id) {
// // return res
// // .status(403)
// // .json({ error: "You can only edit the hospital you have created" });
// // }
// const validColumns = new Set([
// "name_hospital",
// "subdomain",
// "primary_admin_email",
// "primary_admin_password",
// "primary_color",
// "secondary_color",
// "logo_url",
// "status",
// "onboarding_status",
// "admin_name",
// "mobile_number",
// "location",
// "super_admin_id",
// "hospital_code",
// ]);
// // Ensure no extra fields are present
// for (const key of Object.keys(updateData)) {
// if (!validColumns.has(key)) {
// return res.status(400).json({ error: `Invalid field: ${key}` });
// }
// }
// // Build the SQL SET clause dynamically
// const fields = [];
// const values = [];
// for (const [key, value] of Object.entries(updateData)) {
// fields.push(`${key} = ?`);
// values.push(value);
// }
// values.push(id); // Add the hospital ID to the values array for the WHERE clause
// // Construct the SQL query
// const query = `UPDATE hospitals SET ${fields.join(", ")} WHERE id = ?`;
// const result = await db.query(query, values);
// const queryhspt = `SELECT * FROM hospitals WHERE id = ?`;
// const resulthspt = await db.query(queryhspt, [id]);
// // Check if any row was updated
// if (result.affectedRows === 0) {
// return res
// .status(404)
// .json({ error: "Hospital not found or no changes made" });
// }
// res
// .status(200)
// .json({ message: "Hospital updated successfully!", data: resulthspt });
// } catch (error) {
// console.error("Error updating hospital:", error.message);
// res.status(500).json({ error: "Internal server error" });
// }
// };
exports.deleteHospital = async (req, res) => {
const { id } = req.params;
console.log("user details ", req.user);
const hospitalId = id;
if (!id) {
return res.status(400).json({ error: "hospital ID is required" });
}
// Fetch the hospital to validate ownership
const hospitalQuery = "SELECT hospital_code, super_admin_id FROM hospitals WHERE id = ?";
const hospitalResult = await db.query(hospitalQuery, [id]);
console.log("requested user", req.user);
if (hospitalResult.length === 0) {
return res.status(404).json({ error: "Hospital not found" });
}
if (req.user.id !== hospitalResult[0].super_admin_id) {
return res
.status(403)
.json({ error: "You can only delete the hospital you have created" });
}
// Ensure the authenticated user is either Admin or Superadmin
if (!["Spurrinadmin", 6].includes(req.user.role)) {
return res
.status(403)
.json({ error: "You are not authorized to delete hospitals" });
}
// Check for dependent records
const [qaCount] = await db.query(
"SELECT COUNT(*) AS count FROM questions_answers WHERE document_id IN (SELECT id FROM documents WHERE hospital_id = ?)",
[hospitalId]
);
const [qaPageCount] = await db.query(
"SELECT COUNT(*) AS count FROM document_pages WHERE document_id IN (SELECT id FROM documents WHERE hospital_id = ?)",
[hospitalId]
);
const [metadataCount] = await db.query(
"SELECT COUNT(*) AS count FROM document_metadata WHERE document_id IN (SELECT id FROM documents WHERE hospital_id = ?)",
[hospitalId]
);
const [documentsCount] = await db.query(
"SELECT COUNT(*) AS count FROM documents WHERE hospital_id = ?",
[hospitalId]
);
const [appUsersCount] = await db.query(
"SELECT COUNT(*) AS count FROM app_users WHERE hospital_code = (SELECT hospital_code FROM hospitals WHERE id = ?)",
[hospitalId]
);
console.log('qaCount', qaCount, '\nqaPageCount', qaPageCount, '\nmetadataCount', metadataCount, '\ndocumentsCount', documentsCount, '\nappUsersCount', appUsersCount);
// // If any dependent records exist, block deletion
// if (qaCount.count > 0 || metadataCount.count > 0 || documentsCount.count > 0 || appUsersCount.count > 0 || qaPageCount.count > 0) {
// return res
// .status(403)
// .json({ error: "Can not delete hospital dependent records found" });
// }
try {
// Unlink (delete) the associated file if it exists
// Fetch all documents related to the hospital
const documents = await db.query(
"SELECT id, file_url FROM documents WHERE hospital_id = ?",
[id]
);
console.log("doc ids ", documents);
// Delete document files dynamically
for (const document of documents) {
if (document.file_url) {
const filePath = path.join(
__dirname,
"..",
"uploads",
document.file_url.replace(/^\/uploads\//, "")
);
try {
await fs.promises.access(filePath, fs.constants.F_OK);
await fs.promises.unlink(filePath);
console.log("File deleted successfully:", filePath);
} catch (err) {
console.error(`Error deleting or accessing file ${filePath}: ${err.message}`);
}
}
}
// Delete document-related records first
await db.query(
"DELETE FROM questions_answers WHERE document_id IN (SELECT id FROM documents WHERE hospital_id = ?)",
[id]
);
console.log('Deleted questions_answers successfully');
await db.query(
"DELETE FROM document_metadata WHERE document_id IN (SELECT id FROM documents WHERE hospital_id = ?)",
[id]
);
console.log('Deleted document_metadata successfully');
await db.query(
"DELETE FROM document_pages WHERE document_id IN (SELECT id FROM documents WHERE hospital_id = ?)",
[id]
);
console.log('Deleted document_pages successfully');
await db.query("DELETE FROM onboarding_steps WHERE user_id IN (SELECT id from hospital_users WHERE hospital_code = ?)", [
hospitalResult[0].hospital_code,
]);
console.log('Deleted onboarding steps successfully');
// Now delete the documents themselves
await db.query("DELETE FROM documents WHERE hospital_id = ?", [id]);
console.log('Deleted documents successfully');
// Now delete hospital_users AFTER documents (because of FK uploaded_by)
await db.query("DELETE FROM hospital_users WHERE hospital_code = ?", [
hospitalResult[0].hospital_code,
]);
console.log('Deleted hospital_users successfully');
// Then delete app_users
await db.query("DELETE FROM app_users WHERE hospital_code = ?", [
hospitalResult[0].hospital_code,
]);
console.log('Deleted app users successfully');
// And finally interaction_logs
await db.query("DELETE FROM interaction_logs WHERE hospital_code = ?", [
hospitalResult[0].hospital_code,
]);
console.log('Deleted interaction logs successfully');
} catch (error) {
console.error("Error deleting dependent records:", error.message);
return res.status(500).json({ error: "Failed to delete dependent records" });
}
// Now delete the hospital
const deleteQuery = "DELETE FROM hospitals WHERE id = ?";
const result = await db.query(deleteQuery, [id]);
if (result.affectedRows === 0) {
return res.status(404).json({ message: "hospital not found" });
}
res.status(200).json({ message: "Hospital deleted successfully!" });
};
exports.getAllHospitalUsers = async (req, res) => {
try {
// Fetch all users from the hospital_users table
const hospitalUsers = await db.query(`
SELECT
u.id,
u.hospital_id,
u.email,
u.role_id,
r.name AS role_name,
u.status,
u.created_at,
u.updated_at
FROM
hospital_users u
JOIN
roles r
ON
u.role_id = r.id
`);
res.status(200).json({
message: "Hospital users fetched successfully!",
data: hospitalUsers,
});
} catch (error) {
console.error("Error fetching hospital users:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
// get colors
exports.getColorsFromHospital = async (req, res) => {
try {
const { id } = req.user; // Get the hospital ID from the route parameter
console.log("requested user", req.user);
// Ensure no extra fields are present
if (!["Superadmin", 7].includes(req.user.role)) {
return res
.status(403)
.json({ error: "You are not authorized to access hospital's colors!!" });
}
const queryhspt_users = `SELECT hospital_id FROM hospital_users WHERE id = ?`;
const resulthspt_users = await db.query(queryhspt_users, [id]);
const queryhspt = `SELECT primary_color, secondary_color FROM hospitals WHERE id = ?`;
const resulthspt = await db.query(queryhspt, [
resulthspt_users[0].hospital_id,
]);
console.log("resulthspt", resulthspt);
if (resulthspt.length === 0) {
return res.status(404).json({ error: "Hospital not found" });
}
// Check if any row was updated
res.status(200).json({
message: "Hospital colors fetched successfully!",
data: resulthspt,
});
} catch (error) {
console.error("Error fetching hospital:", error.message);
res.status(500).json({ error: "Internal server error" });
}
};
// change password of super_admins/hospitals only spurrin have access to do so
exports.changePassword = async (req, res) => {
try {
const id = req.user.id; // Get the user ID from the route parameter
const { new_password } = req.body; // Get the new password from the request body
const authHeader = req.headers.authorization; // Get the token from the Authorization header
// Validate input
if (!new_password) {
return res.status(400).json({ error: "New password is required" });
}
// Ensure the token is present
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "Authorization token is required" });
}
const token = authHeader.split(" ")[1]; // Extract token from "Bearer <token>"
let decodedToken;
try {
decodedToken = jwt.verify(token, process.env.JWT_ACCESS_TOKEN_SECRET); // Decode the token
} catch (err) {
return res.status(401).json({ error: "Invalid or expired token" });
}
// Ensure the decoded token's user ID matches the route parameter
if (parseInt(id, 10) !== decodedToken.id) {
return res
.status(403)
.json({ error: "Token user does not match the requested user" });
}
// Convert ID to integer and validate
const numericId = parseInt(id, 10);
if (isNaN(numericId)) {
return res.status(400).json({ error: "Invalid user ID" });
}
// Fetch the user from the database to ensure they exist
const userQuery = `
SELECT id FROM app_users WHERE id = ?
`;
const [userResult] = await db.query(userQuery, [numericId]);
if (!userResult || userResult.length === 0) {
return res.status(404).json({ error: "User not found" });
}
// Hash the new password
const hashedNewPassword = await bcrypt.hash(new_password, 10);
// Update the password in the database
const updatePasswordQuery = `
UPDATE app_users SET hash_password = ? WHERE id = ?
`;
await db.query(updatePasswordQuery, [hashedNewPassword, numericId]);
res.status(200).json({ message: "Password updated successfully!" });
} catch (error) {
console.error("Error updating password:", error.message, error.stack);
res.status(500).json({ error: "Internal server error" });
}
};
const crypto = require("crypto");
function generateRandomPassword(length = 12) {
return crypto
.randomBytes(Math.ceil(length / 2))
.toString("hex")
.slice(0, length);
}
// Generate and print the password
const randomPassword = generateRandomPassword();
console.log("Generated Password:", randomPassword);
exports.sendTempPassword = 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, primary_admin_email ,name_hospital,admin_name FROM hospitals WHERE primary_admin_email = ?",
[email]
);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
const hsptuser = await db.query(
"SELECT id, email FROM hospital_users WHERE email = ?",
[email]
);
if (!hsptuser.length) {
return res.status(404).json({ error: "User not found" });
}
const hsptId = user[0].id;
const hsptUsrId = hsptuser[0].id;
const randomPassword = generateRandomPassword();
const hashedPassword = await bcrypt.hash(randomPassword, 10);
const expiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000); // OTP expires in 1 hour
const type = "temp";
await db.query(
"UPDATE hospitals SET temporary_password = ?, expires_at = ?, type = ? WHERE id = ?",
[randomPassword, expiresAt, type, hsptId]
);
await db.query(
"UPDATE hospital_users SET temporary_password = ?, expires_at = ?, type = ? WHERE id = ?",
[randomPassword, expiresAt, type, hsptUsrId]
);
// // // Send OTP via email
const info = await sendMail(
email,
user[0].name_hospital,
user[0].admin_name,
randomPassword
);
res.json({ message: "temporary password generated successfully",email_status: info.response });
} catch (error) {
console.error("Error sending OTP:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.changeTempPassword = async (req, res) => {
try {
const { email, temp_password, new_password } = req.body;
// Validate inputs
if (!email || !temp_password || !new_password) {
return res.status(400).json({
error: "Email, Temporary password, and new password are required",
});
}
const user = await db.query(
"SELECT id, temporary_password, expires_at, type FROM hospitals WHERE primary_admin_email = ?",
[email]
);
if (!user.length) {
return res.status(404).json({ error: "User not found" });
}
const hsptuser = await db.query(
"SELECT id, temporary_password, expires_at, type FROM hospital_users WHERE email = ?",
[email]
);
if (!hsptuser.length) {
return res.status(404).json({ error: "User not found" });
}
const isMatch = temp_password === user[0].temporary_password;
// check the password match
if (!isMatch) {
return res.status(400).json({ error: "Invalid temporary password" });
}
// Check if temporary password is expired
if (new Date() > new Date(user[0].expires_at)) {
return res
.status(400)
.json({ error: "temporary password 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 hospitals SET primary_admin_password = ?, expires_at = ? ,type = NULL, temporary_password = NULL WHERE id = ?",
[hashedPassword, new Date(Date.now()), user[0].id]
);
await db.query(
"UPDATE hospital_users SET hash_password = ?, expires_at = ? ,type = NULL, temporary_password = NULL WHERE id = ?",
[hashedPassword, new Date(Date.now()), hsptuser[0].id]
);
res.json({ message: "Password changed successfully!" });
} catch (error) {
console.error("Error resetting password:", error);
res.status(500).json({ error: "Internal server error" });
}
};
async function sendMail(email, hospital_name, adminName, randomPassword) {
const mailOptions = {
from: "kavya.j@tech4biz.io", // Sender's email
to: email, // Recipient's email
subject: "Spurrinai temporary password", // 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 - 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</h1>
</div>
<div class="email-content">
<div class="greeting">Hello ${adminName},</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>Your temporary password:</p>
<div class="code">${randomPassword}</div>
<p>use same password to generate new password</p>
</div>
<a href="#" class="reset-button">Copy 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);
return info; // Return the info object for further processing if needed
} catch (error) {
console.error(`Error sending email to ${email}:`, error);
return error
}
}
exports.updateHospitalName = async (req, res) => {
console.log("requested user", req.user);
const hospital_user_id = req.user.id;
const { hospital_name } = req.body;
// Ensure the authenticated user is either Admin or Superadmin
if (!["Superadmin", 7].includes(req.user.role)) {
return res
.status(403)
.json({ error: "You are not authorized to update admins name" });
}
const queryuser = 'SELECT hospital_id FROM hospital_users WHERE id = ?';
const [rows] = await db.execute(queryuser, [hospital_user_id]);
console.log(rows[0]?.hospital_id);
const query = "UPDATE hospital_name SET name = ? WHERE id = ?";
const values = [hospital_name, rows[0]?.hospital_id];
await db.query(query, values);
res.json({ message: "Name changed successfully!" });
}
// password change for admins and viewers
exports.sendTemporaryPassword = async (req, res) => {
try {
const { email } = req.body;
// Validate email input
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
const hsptuser = await db.query(
"SELECT id,hospital_id, hash_password, name, expires_at, type FROM hospital_users WHERE email = ? AND role_id IN (8, 9)",
[email]
);
if (!hsptuser.length) {
return res.status(404).json({ error: "User not found" });
}
const hsptUsrId = hsptuser[0].id;
const hsptId = hsptuser[0].hospital_id;
const randomPassword = generateRandomPassword();
const hashedPassword = await bcrypt.hash(randomPassword, 10);
const expiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000); // OTP expires in 1 hour
const type = "temp";
await db.query(
"UPDATE hospital_users SET hash_password = ?, expires_at = ?, type = ? WHERE id = ?",
[hashedPassword, expiresAt, type, hsptUsrId]
);
const hspt = await db.query(
"SELECT name_hospital FROM hospitals WHERE id = ?",
[hsptId]
);
// // // Send OTP via email
const info =await sendMail(
email,
hspt[0].name_hospital,
hsptuser[0].name,
randomPassword
);
res.json({ message: "temporary password gerated successfully",email_status: info.response });
} catch (error) {
console.error("Error sending OTP:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.changeTempPasswordAdminsViewers = async (req, res) => {
try {
const { email, temp_password, new_password } = req.body;
console.log("incoming data ", req.body);
// Validate inputs
if (!email || !temp_password || !new_password) {
return res.status(400).json({
error: "Email, Temporary password, and new password are required",
});
}
// Ensure the user has the proper role
const hsptuser = await db.query(
"SELECT id, temporary_password, expires_at, type FROM hospital_users WHERE email = ? AND role_id IN (8, 9)",
[email]
);
if (!hsptuser.length) {
return res.status(404).json({ error: "Email not found" });
}
const isMatch = await bcrypt.compare(
temp_password,
hsptuser[0].temporary_password
);
// Check if temporary password matches
if (!isMatch) {
return res.status(400).json({ error: "Invalid temporary password" });
}
// Check if temporary password is expired
if (new Date() > new Date(hsptuser[0].expires_at)) {
return res
.status(400)
.json({ error: "temporary password 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 hospital_users SET hash_password = ?, expires_at = ? ,type = NULL, temporary_password = NULL WHERE id = ?",
[hashedPassword, new Date(Date.now()), hsptuser[0].id]
);
res.json({ message: "Password changed successfully!" });
} catch (error) {
console.error("Error resetting password:", error);
res.status(500).json({ error: "Internal server error" });
}
};
// check app user notification
exports.checkNewAppUser = async (req, res) => {
if (!["Superadmin", "Admin", 7, 8].includes(req.user.role)) {
return res
.status(403)
.json({ error: "You are not authorized" });
}
try {
const { hospital_code } = req.body;
// Validate inputs
if (!hospital_code) {
return res.status(400).json({ error: "hospital code is required" });
}
// notify hospital only if notification is not checked
const appUser = await db.query(
"SELECT * FROM app_users WHERE hospital_code = ? AND checked = 0",
[hospital_code]
);
if (!appUser.length) {
return res.status(404).json({ error: "No new user found" });
}
res.json({ message: "new notification found", appUser });
} catch (error) {
console.error("Error checking new notification:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.updateAppUserChecked = async (req, res) => {
if (!["Superadmin", "Admin", 7, 8].includes(req.user.role)) {
return res
.status(403)
.json({ error: "You are not authorized" });
}
try {
const id = req.params.id;
if (!id) {
return res.status(400).json({ error: "User ID is required" });
}
const result = await db.query("UPDATE app_users SET checked = 1 WHERE id = ?", [id]);
if (result.affectedRows === 0) {
return res.status(404).json({ error: "User not found or already checked" });
}
res.json({ message: "User checked status updated successfully", updatedUserId: id });
} catch (error) {
console.error("Error updating checked status:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.interactionLogs = async (req, res) => {
const { hospital_code, app_user_id } = req.body;
if (!["Superadmin", 7].includes(req.user.role)) {
return res.status(403).json({ error: "You are not authorized" });
}
try {
// Validate inputs
if (!hospital_code && !app_user_id) {
return res
.status(400)
.json({ error: "hospital code or app user id is required" });
}
let intLogs;
// Build dynamic query based on inputs
let baseQuery = `
SELECT il.*, au.email, au.username
FROM interaction_logs il
LEFT JOIN app_users au ON il.app_user_id = au.id
WHERE 1=1
`;
const params = [];
if (hospital_code) {
baseQuery += ` AND il.hospital_code = ?`;
params.push(hospital_code);
}
if (app_user_id) {
baseQuery += ` AND il.app_user_id = ?`;
params.push(app_user_id);
}
intLogs = await db.query(baseQuery, params);
if (!intLogs.length) {
return res.status(200).json({ error: "No logs found" });
}
res.json({ message: "log data found", intLogs });
} catch (error) {
console.error("Error fetching logs:", error);
res.status(500).json({ error: "Internal server error" });
}
};
exports.updatePublicSignup = async (req, res) => {
try {
const { id } = req.params;
const { enabled } = req.body;
// Validate input
if (typeof enabled !== 'boolean') {
return res.status(400).json({
error: "Invalid input. 'enabled' must be a boolean value"
});
}
console.log("req user-----", req.user)
// Check if user has permission
if (!["Spurrinadmin", "Superadmin", 7, 6].includes(req.user.role)) {
return res.status(403).json({
error: "You are not authorized to update public signup settings"
});
}
// If user is Superadmin, verify they own the hospital
if (req.user.role === "Superadmin") {
const hospital = await db.query(
"SELECT id FROM hospitals WHERE id = ?",
[id]
);
if (!hospital.length) {
return res.status(403).json({
error: "hospital not found"
});
}
if (id != req.user.hospital_id) {
return res.status(403).json({
message: "You can only update public signup settings for your own hospital"
});
}
}
const result = await db.query(
'UPDATE hospitals SET publicSignupEnabled = ? WHERE id = ?',
[enabled, id]
);
if (result.affectedRows > 0) {
res.status(200).json({
status: 'success',
message: 'Hospital signup settings updated successfully.',
data: {
id,
publicSignupEnabled: enabled
}
});
}
else {
res.status(404).json({
status: 'error',
message: 'Hospital not found or no changes made.',
data: null
});
}
} catch (error) {
console.error("Error updating public signup setting:", error);
if (error instanceof ValidationError) {
res.status(400).json({ error: error.message });
} else {
res.status(500).json({ error: "Internal server error" });
}
}
};
exports.getPublicSignup = async (req, res) => {
try {
const { id } = req.params;
// Check if user has permission
if (!["Spurrinadmin", "Superadmin", 7, 6].includes(req.user.role)) {
return res.status(403).json({
error: "You are not authorized to update public signup settings"
});
}
// If user is Superadmin, verify they own the hospital
if (req.user.role === "Superadmin") {
const hospital = await db.query(
"SELECT id FROM hospitals WHERE id = ?",
[id]
);
if (!hospital.length) {
return res.status(403).json({
error: "hospital not found"
});
}
if (id != req.user.hospital_id) {
return res.status(403).json({
message: "You can only get public signup settings for your own hospital"
});
}
}
const result = await db.query(
'SELECT publicSignupEnabled from hospitals WHERE id = ?',
[id]
);
console.log("result----",result)
if (result.length > 0) {
res.status(200).json({
message: 'data fetched successfully.',
result
});
}
else {
res.status(200).json({
status: 'Not found',
message: 'Hospital not found or no changes made.',
});
}
} catch (error) {
console.error("Error updating public signup setting:", error);
if (error instanceof ValidationError) {
res.status(400).json({ error: error.message });
} else {
res.status(500).json({ error: "Internal server error" });
}
}
};