510 lines
20 KiB
JavaScript
510 lines
20 KiB
JavaScript
const fs = require("fs");
|
|
const https = require("https");
|
|
const WebSocket = require("ws");
|
|
const jwt = require("jsonwebtoken");
|
|
const fetch = require("node-fetch");
|
|
const db = require("../config/database");
|
|
|
|
const base_url = "https://backend.spurrinai.com";
|
|
|
|
const server = https.createServer({
|
|
cert: fs.readFileSync("/home/ubuntu/spurrin-backend/certificates/fullchain.pem"),
|
|
key: fs.readFileSync("/home/ubuntu/spurrin-backend/certificates/privkey.pem")
|
|
});
|
|
|
|
const wss = new WebSocket.Server({ server, perMessageDeflate: false });
|
|
const userSockets = new Map();
|
|
|
|
console.log("✅ Secure WebSocket Server running on wss://0.0.0.0:40520");
|
|
|
|
wss.on("connection", (ws) => {
|
|
console.log("🔌 New client connected to secondary WebSocket");
|
|
|
|
ws.on("message", async (message) => {
|
|
const data = JSON.parse(message);
|
|
|
|
if (data.token && !ws.userId) {
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
ws.userId = decoded.id;
|
|
userSockets.set(decoded.id, ws);
|
|
} catch {
|
|
ws.userId = null;
|
|
}
|
|
}
|
|
|
|
if (data.event === "check-token-expiry") {
|
|
let decoded;
|
|
try {
|
|
decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
if (!decoded.exp) {
|
|
emitEvent("check-token-expiry", { expired: 1, message: 'Expiry not set to token' }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
const timeLeft = decoded.exp - currentTime;
|
|
|
|
if (timeLeft > 0) {
|
|
emitEvent("check-token-expiry", { expired: 0, message: `Token expires in ${Math.floor(timeLeft / 60)} minutes` }, decoded.id);
|
|
} else {
|
|
emitEvent("check-token-expiry", { expired: 1, message: "Token expired, please relogin" }, decoded.id);
|
|
}
|
|
} catch (error) {
|
|
emitEvent("check-token-expiry", { expired: 1, message: 'Token malformed', error }, ws.userId);
|
|
}
|
|
}
|
|
|
|
if (data.event === "check-latest-token") {
|
|
if (!data.token) {
|
|
emitEvent("check-latest-token", { message: 'Access token required' }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
const decoded = jwt.decode(data.token);
|
|
if (!decoded) {
|
|
emitEvent("check-latest-token", { message: "Invalid token format" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
ws.userId = decoded.id;
|
|
userSockets.set(decoded.id, ws);
|
|
|
|
let table;
|
|
if (decoded.role === "Spurrinadmin") table = "super_admins";
|
|
else if (["Admin", "Viewer", "Superadmin", 7, 8, 9].includes(decoded.role)) table = "hospital_users";
|
|
else if (decoded.role === "AppUser") table = "app_users";
|
|
else {
|
|
emitEvent("check-latest-token", { message: "Invalid role" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = await db.query(`SELECT access_token FROM ${table} WHERE id = ?`, [decoded.id]);
|
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
const timeLeft = decoded.exp - currentTime;
|
|
|
|
if (result.length > 0 && result[0].access_token === data.token && timeLeft > 0) {
|
|
emitEvent("check-latest-token", { valid: 1, expired: 0, message: 'Token is valid' }, decoded.id);
|
|
} else {
|
|
emitEvent("check-latest-token", { valid: 0, expired: 0, message: 'Invalid token or expired' }, decoded.id);
|
|
}
|
|
} catch (error) {
|
|
emitEvent("check-latest-token", { valid: 0, expired: 0, message: "DB Error", error }, decoded.id);
|
|
}
|
|
}
|
|
|
|
if (data.event === "check-notification") {
|
|
try {
|
|
const response = await fetch(base_url + "/api/hospitals/check-user-notification", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${data.token}`
|
|
},
|
|
body: JSON.stringify({ hospital_code: data.hospital_code })
|
|
});
|
|
|
|
const result = await response.json();
|
|
emitEvent("check-notification", { data: result, message: "New app users" }, ws.userId);
|
|
} catch (error) {
|
|
emitEvent("check-notification", { message: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
if (data.event === "get-hospital-users") {
|
|
if (!data.token) {
|
|
emitEvent("get-hospital-users", { error: "Token missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
if (decoded.role !== 'Spurrinadmin' && decoded.role !== 6) {
|
|
emitEvent("get-hospital-users", { error: "Unauthorized access" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
const users = await db.query("SELECT * FROM hospital_users");
|
|
emitEvent("get-hospital-users", { data: users }, ws.userId);
|
|
} catch (error) {
|
|
emitEvent("get-hospital-users", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
if (data.event === "get-forwarded-feedbacks") {
|
|
if (!data.token) {
|
|
emitEvent("get-forwarded-feedbacks", { error: "Token missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
if (decoded.role !== 'Spurrinadmin' && decoded.role !== 6) {
|
|
emitEvent("get-forwarded-feedbacks", { error: "Unauthorized access" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
const query = `
|
|
SELECT
|
|
f.sender_type,
|
|
f.sender_id,
|
|
f.receiver_type,
|
|
f.receiver_id,
|
|
f.rating,
|
|
f.purpose,
|
|
f.information_received,
|
|
f.feedback_text,
|
|
f.created_at,
|
|
f.is_forwarded,
|
|
f.improvement,
|
|
h.name_hospital as sender_hospital,
|
|
h.hospital_code
|
|
FROM feedback f
|
|
LEFT JOIN hospitals h ON f.sender_id = h.id AND f.sender_type = 'hospital'
|
|
WHERE f.receiver_type = 'spurrin'
|
|
ORDER BY f.created_at DESC
|
|
`;
|
|
|
|
const feedbacks = await db.query(query);
|
|
emitEvent("get-forwarded-feedbacks", {
|
|
message: "Forwarded feedbacks fetched successfully.",
|
|
data: feedbacks
|
|
}, ws.userId);
|
|
} catch (error) {
|
|
emitEvent("get-forwarded-feedbacks", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
// This event retrieves all feedback entries submitted by app users (sender_type = 'appuser') to a specific hospital (receiver_type = 'hospital') based on the hospital's hospital_code, which is derived from the JWT token provided by the user.
|
|
if (data.event === "get-app-user-byhospital-feedback") {
|
|
if (!data.token) {
|
|
emitEvent("get-app-user-byhospital-feedback", { error: "Token missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
|
|
// Only hospital users (role 7, 8, or 9) are allowed
|
|
if (!["Superadmin", "Admin", 7, 8].includes(decoded.role)) {
|
|
emitEvent("get-app-user-byhospital-feedback", { error: "Unauthorized access" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
console.log("Decoded token-----------------:", decoded);
|
|
|
|
const email = decoded.email;
|
|
const userId = decoded.id;
|
|
|
|
// Fetch hospital ID using the code
|
|
const hospitalCheck = await db.query(
|
|
"SELECT id FROM hospitals WHERE primary_admin_email = ?",
|
|
[email]
|
|
);
|
|
|
|
if (hospitalCheck.length === 0) {
|
|
emitEvent("get-app-user-byhospital-feedback", { error: "Hospital not found" }, userId);
|
|
return;
|
|
}
|
|
|
|
const hospitalId = hospitalCheck[0].id;
|
|
|
|
const query = `
|
|
SELECT
|
|
f.feedback_id,
|
|
f.sender_type,
|
|
f.sender_id,
|
|
f.receiver_type,
|
|
f.receiver_id,
|
|
f.rating,
|
|
f.purpose,
|
|
f.information_received,
|
|
f.feedback_text,
|
|
f.improvement,
|
|
f.created_at,
|
|
f.is_forwarded,
|
|
au.username as user_name,
|
|
au.email as user_email
|
|
FROM feedback f
|
|
LEFT JOIN app_users au ON f.sender_id = au.id AND f.sender_type = 'appuser'
|
|
WHERE f.receiver_type = 'hospital' AND f.receiver_id = ?
|
|
ORDER BY f.created_at DESC
|
|
`;
|
|
|
|
const feedbacks = await db.query(query, [hospitalId]);
|
|
|
|
emitEvent("get-app-user-byhospital-feedback", {
|
|
message: "Hospital feedbacks fetched successfully.",
|
|
data: feedbacks
|
|
}, userId);
|
|
|
|
} catch (error) {
|
|
emitEvent("get-app-user-byhospital-feedback", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
if (data.event === "get-documents-by-hospital") {
|
|
if (!data.token || !data.hospital_id) {
|
|
emitEvent("get-documents-by-hospital", { error: "Token or hospital_id missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
const allowedRoles = ['Admin', 'Superadmin', 'Viewer', 7, 8, 9];
|
|
|
|
// Role-based access check
|
|
if (!allowedRoles.includes(decoded.role)) {
|
|
emitEvent("get-documents-by-hospital", { error: "You are not authorized to view documents" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
// Hospital access validation
|
|
const requestedHospitalId = parseInt(data.hospital_id, 10);
|
|
if (decoded.hospital_id !== requestedHospitalId) {
|
|
emitEvent("get-documents-by-hospital", { error: "Unauthorized hospital access" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
// Fetch documents for hospital
|
|
const documents = await db.query(
|
|
"SELECT * FROM documents WHERE hospital_id = ?",
|
|
[requestedHospitalId]
|
|
);
|
|
|
|
emitEvent("get-documents-by-hospital", {
|
|
message: "Documents fetched successfully.",
|
|
documents
|
|
}, decoded.id);
|
|
|
|
} catch (error) {
|
|
emitEvent("get-documents-by-hospital", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
if (data.event === "app-usersby-hospitalid") {
|
|
if (!data.token || !data.id) {
|
|
emitEvent("app-usersby-hospitalid", { error: "Token or hospital ID missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
const userRole = decoded.role;
|
|
|
|
// Only allowed roles
|
|
if (!["Superadmin", "Admin", 8, 9].includes(userRole)) {
|
|
emitEvent("app-usersby-hospitalid", { error: "Unauthorized to view app users" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
// Fetch hospital_code using hospital id
|
|
const query1 = `SELECT * FROM hospitals WHERE id = ?`;
|
|
const result1 = await db.query(query1, [data.id]);
|
|
|
|
if (!result1 || !result1[0].hospital_code) {
|
|
emitEvent("app-usersby-hospitalid", { error: "Hospital not found" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
|
|
const hospitalCode = result1[0].hospital_code;
|
|
|
|
// Fetch app users for that hospital_code
|
|
const query2 = `SELECT * FROM app_users WHERE hospital_code = ?`;
|
|
const users = await db.query(query2, [hospitalCode]);
|
|
|
|
if (users.length === 0) {
|
|
emitEvent("app-usersby-hospitalid", { message: "No app users found" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
emitEvent("app-usersby-hospitalid", {
|
|
message: "App users fetched successfully",
|
|
data: users
|
|
}, decoded.id);
|
|
|
|
} catch (error) {
|
|
emitEvent("app-usersby-hospitalid", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
if (data.event === "get-signup-notifications") {
|
|
|
|
if (!data.token) {
|
|
emitEvent("get-signup-notifications", { error: "Token missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
const allowedRoles = ['Admin', 'Superadmin', 8, 7];
|
|
|
|
// Role-based access check
|
|
if (!allowedRoles.includes(decoded.role)) {
|
|
emitEvent("get-signup-notifications", { error: "You are not authorized!" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
// Fetch hospital_code from the DB
|
|
const result = await db.query(
|
|
"SELECT hospital_code FROM hospital_users WHERE id = ?",
|
|
[decoded.id]
|
|
);
|
|
|
|
// Validate result
|
|
if (!result || result.length === 0 || !result[0].hospital_code) {
|
|
emitEvent("get-signup-notifications", { error: "Hospital code not found." }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
const hospital_code = result[0].hospital_code;
|
|
|
|
// Fetch signup notifications
|
|
const notifications = await db.query(
|
|
"SELECT * FROM app_users WHERE hospital_code = ? AND checked = 0",
|
|
[hospital_code]
|
|
);
|
|
|
|
emitEvent("get-signup-notifications", {
|
|
message: "Notifications fetched successfully.",
|
|
notifications
|
|
}, decoded.id);
|
|
|
|
} catch (error) {
|
|
console.error("Error fetching signup notifications:", error);
|
|
emitEvent("get-signup-notifications", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
|
|
if (data.event === "get-app-queries") {
|
|
|
|
if (!data.token || (!data.hospital_code || !data.app_user_id)) {
|
|
emitEvent("get-app-queries", { error: "Token missing or hospital_code or app_user_id missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
const allowedRoles = ['Admin', 'Superadmin', 8, 7];
|
|
|
|
// Role-based access check
|
|
if (!allowedRoles.includes(decoded.role)) {
|
|
emitEvent("get-app-queries", { error: "You are not authorized!" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let interaction_logs;
|
|
// Fetch notifications of new signup
|
|
if (data.hospital_code.length == 0) {
|
|
interaction_logs = await db.query(
|
|
"SELECT * FROM interaction_logs WHERE app_user_id = ?",
|
|
[data.app_user_id]
|
|
);
|
|
}
|
|
else if (data.app_user_id.length == 0) {
|
|
interaction_logs = await db.query(
|
|
"SELECT * FROM interaction_logs WHERE app_user_id = ?",
|
|
[data.app_user_id]
|
|
);
|
|
}
|
|
|
|
emitEvent("get-app-queries", {
|
|
message: "interaction logs fetched successfully.",
|
|
interaction_logs
|
|
}, decoded.id);
|
|
|
|
} catch (error) {
|
|
emitEvent("get-app-queries", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
|
|
if (data.event === "get-signup-notifications") {
|
|
if (!data.token) {
|
|
emitEvent("get-signup-notifications", { error: "Token missing" }, ws.userId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET);
|
|
const allowedRoles = ['Admin', 'Superadmin', 8, 7];
|
|
|
|
// Role-based access check
|
|
if (!allowedRoles.includes(decoded.role)) {
|
|
emitEvent("get-signup-notifications", { error: "You are not authorized!" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
console.log("decoded token ----", decoded)
|
|
|
|
// Fetch hospital_code from hospitals table
|
|
const result = await db.query(
|
|
"SELECT hospital_code FROM hospital_users WHERE id = ?",
|
|
[decoded.id]
|
|
);
|
|
|
|
if (!result || result.length === 0 || !result[0].hospital_code) {
|
|
emitEvent("get-signup-notifications", { error: "Hospital code not found" }, decoded.id);
|
|
return;
|
|
}
|
|
|
|
const hospital_code = result[0].hospital_code;
|
|
|
|
// Fetch notifications of new signups
|
|
const notifications = await db.query(
|
|
"SELECT * FROM app_users WHERE hospital_code = ? AND checked = 0",
|
|
[hospital_code]
|
|
);
|
|
|
|
emitEvent("get-signup-notifications", {
|
|
message: "Notifications fetched successfully.",
|
|
notifications
|
|
}, decoded.id);
|
|
|
|
} catch (error) {
|
|
emitEvent("get-signup-notifications", { error: error.message }, ws.userId);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
ws.on("close", () => {
|
|
console.log("❌ Client disconnected from secondary WebSocket");
|
|
if (ws.userId && userSockets.has(ws.userId)) {
|
|
userSockets.delete(ws.userId);
|
|
}
|
|
ws.terminate();
|
|
});
|
|
});
|
|
|
|
// Add this function before the server.listen() call
|
|
function emitEvent(event, data, userId = null) {
|
|
if (userId && userSockets.has(userId)) {
|
|
const client = userSockets.get(userId);
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(JSON.stringify({ event, data }));
|
|
}
|
|
} else {
|
|
wss.clients.forEach((client) => {
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(JSON.stringify({ event, data }));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
server.listen(40520, () => {
|
|
console.log("📡 Secure WebSocket server listening on wss://backend.spurrinai.com:40520");
|
|
});
|
|
|
|
module.exports = { wss }; |