This commit is contained in:
Aakash Mittal 2025-11-24 16:53:28 +05:30
commit 68d81f8e1c
11 changed files with 109 additions and 99 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules node_modules
logs logs
.env .env
public/builds

View File

@ -1,17 +1,17 @@
import PasswordResetRepository from "../repository/passwordReset.repository.js"; import PasswordResetRepository from "../repository/passwordReset.repository.js";
import PasswordResetService from "../services/passwordReset.service.js"; import PasswordResetService from "../services/passwordReset.service.js";
import EmailService from "../services/email.service.js"; // import EmailService from "../services/email.service.js";
import logger from "../config/logger.js"; import logger from "../config/logger.js";
const passwordResetService = new PasswordResetService( const passwordResetService = new PasswordResetService(
new PasswordResetRepository(), new PasswordResetRepository(),
new EmailService({ // new EmailService({
host: process.env.SMTP_HOST, // host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT, // port: process.env.SMTP_PORT,
user: process.env.SMTP_USER, // user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS, // pass: process.env.SMTP_PASS,
from: process.env.EMAIL_FROM, // from: process.env.EMAIL_FROM,
}) // })
); );
// VA: Request password reset // VA: Request password reset

View File

@ -189,47 +189,47 @@ async function va_project_log_callback(req, res) {
const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId }) const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
// console.log(createdLog) // console.log(createdLog)
// try { try {
// const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`;
// const sfData = { const sfData = {
// "_id": createdLog._id, "_id": createdLog._id,
// "loggedType": type, "loggedType": type,
// "vaId": vaId, "vaId": vaId,
// "vaEmail": vaEmail, "vaEmail": vaEmail,
// "projectId": projectId, "projectId": projectId,
// "time": createdLog.time, "time": createdLog.time,
// } }
// const response = await fetch(salesforceUrl, { const response = await fetch(salesforceUrl, {
// method: 'POST', method: 'POST',
// headers: { headers: {
// 'Content-Type': 'application/json', 'Content-Type': 'application/json',
// // Add any required headers for Salesforce authentication // Add any required headers for Salesforce authentication
// // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
// }, },
// body: JSON.stringify(sfData) body: JSON.stringify(sfData)
// }); });
// if (!response.ok) { if (!response.ok) {
// const errorText = await response.text(); const errorText = await response.text();
// throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
// } }
// const result = await response.json(); const result = await response.json();
// // console.log("✅ Successfully synced VA data to Salesforce:", result); // console.log("✅ Successfully synced VA data to Salesforce:", result);
// res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._id, SyncSalesforce: result }); res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._id, SyncSalesforce: result });
// } catch (error) { } catch (error) {
// console.error("Failed to sync with Salesforce:", error.message); console.error("Failed to sync with Salesforce:", error.message);
// console.log(error) console.log(error)
// // Create a custom error to distinguish Salesforce errors // Create a custom error to distinguish Salesforce errors
// const salesforceError = new Error(`Salesforce sync failed: ${error.message}`); const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
// salesforceError.isSalesforceError = true; salesforceError.isSalesforceError = true;
// throw salesforceError; throw salesforceError;
// } }
return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id }); // return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id });
} catch (error) { } catch (error) {
return res.status(500).send("Internal Server Error"); return res.status(500).send("Internal Server Error");

View File

@ -234,7 +234,7 @@ async function removeEntityRelationships(data) {
}} }}
); );
} }
// Remove projectId from VAs // Remove projectId from VAs
if (vaId && Array.isArray(vaId)) { if (vaId && Array.isArray(vaId)) {
// await VAModel.updateMany( // await VAModel.updateMany(

View File

@ -26,19 +26,6 @@ const app = express();
const server = new createServer(app); const server = new createServer(app);
const io = new Server(server); const io = new Server(server);
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
console.log("⚠️ Certificate error:", url);
// Allow for ngrok only
if (url.includes("ngrok")) {
event.preventDefault();
callback(true); // approve the certificate
} else {
callback(false); // reject others
}
});
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
console.log("⚠️ Certificate error:", url); console.log("⚠️ Certificate error:", url);
@ -92,11 +79,12 @@ app.get('/varegister/:sf_token?', (req, res) => {
try { try {
// const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN); // const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
// if (decoded) { if (decoded) {
res.render('va_register', { title: "VA Registration" }); const email = decoded.email;
// } else { res.render('va_register', { title: "VA Registration", email });
// res.render('not_found', { title: "VA Registration" }); } else {
// } res.render('not_found', { title: "VA Registration" });
}
} catch (error) { } catch (error) {
res.render('not_found', { title: "VA Registration" }); res.render('not_found', { title: "VA Registration" });
} }
@ -114,7 +102,8 @@ app.get('/clientregister/:sf_token?', (req, res) => {
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN); const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
if (decoded) { if (decoded) {
res.render('va_client_register', { title: "Client Registration" }); const email = decoded.email;
res.render('va_client_register', { title: "Client Registration", email });
} else { } else {
res.render('not_found', { title: "Client Registration" }); res.render('not_found', { title: "Client Registration" });
} }

View File

@ -2,25 +2,26 @@ import jwt from "jsonwebtoken";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { VAModel, ClientModel } from "../models/vaModel.js"; import { VAModel, ClientModel } from "../models/vaModel.js";
import logger from "../config/logger.js"; import logger from "../config/logger.js";
import axios from "axios";
class PasswordResetService { class PasswordResetService {
constructor(passwordResetRepository, emailService) { constructor(passwordResetRepository) {
this.passwordResetRepository = passwordResetRepository; this.passwordResetRepository = passwordResetRepository;
this.emailService = emailService; // this.emailService = emailService;
} }
async requestPasswordReset(email, userType) { async requestPasswordReset(email, userType) {
try { try {
// Determine which model to use // Determine which model to use
const userModel = userType === 'va' ? VAModel : ClientModel; const userModel = userType === 'va' ? VAModel : ClientModel;
// Check if user exists // Check if user exists
const user = await userModel.findOne({ "userDetails.email": email.toLowerCase() }); const user = await userModel.findOne({ "userDetails.email": email.toLowerCase() });
if (!user) { if (!user) {
logger.info(`Password reset requested for non-existent ${userType}: ${email}`); logger.info(`Password reset requested for non-existent ${userType}: ${email}`);
return { return {
success: false, success: false,
message: "Email not recognized. Please sign up to continue." message: "Email not recognized. Please sign up to continue."
}; };
} }
@ -29,7 +30,7 @@ class PasswordResetService {
// Generate JWT token // Generate JWT token
const token = jwt.sign( const token = jwt.sign(
{ {
email: email.toLowerCase(), email: email.toLowerCase(),
userType: userType, userType: userType,
type: 'password_reset' type: 'password_reset'
@ -60,9 +61,9 @@ class PasswordResetService {
await this.sendResetEmail(email, resetLink, userType); await this.sendResetEmail(email, resetLink, userType);
logger.info(`Password reset token created for ${userType}: ${email}`); logger.info(`Password reset token created for ${userType}: ${email}`);
return { return {
success: true, success: true,
message: "If the email exists, a reset link has been sent" message: "If the email exists, a reset link has been sent"
}; };
} catch (error) { } catch (error) {
@ -92,10 +93,10 @@ class PasswordResetService {
return { valid: false, message: "Invalid or expired token" }; return { valid: false, message: "Invalid or expired token" };
} }
return { return {
valid: true, valid: true,
email: decoded.email, email: decoded.email,
userType: decoded.userType userType: decoded.userType
}; };
} catch (error) { } catch (error) {
@ -129,7 +130,7 @@ class PasswordResetService {
const userModel = userType === 'va' ? VAModel : ClientModel; const userModel = userType === 'va' ? VAModel : ClientModel;
// Check if user exists and has matching token // Check if user exists and has matching token
const user = await userModel.findOne({ const user = await userModel.findOne({
"userDetails.email": decoded.email, "userDetails.email": decoded.email,
passwordResetToken: token passwordResetToken: token
}); });
@ -150,12 +151,12 @@ class PasswordResetService {
await resetRecord.save(); await resetRecord.save();
// Send confirmation email // Send confirmation email
await this.sendPasswordChangedEmail(decoded.email, userType); // await this.sendPasswordChangedEmail(decoded.email, userType);
logger.info(`Password successfully reset for ${userType}: ${decoded.email}`); logger.info(`Password successfully reset for ${userType}: ${decoded.email}`);
return { return {
success: true, success: true,
message: "Password has been reset successfully" message: "Password has been reset successfully"
}; };
} catch (error) { } catch (error) {
@ -166,16 +167,15 @@ class PasswordResetService {
async sendResetEmail(email, resetLink, userType) { async sendResetEmail(email, resetLink, userType) {
try { try {
const userTypeText = userType === 'va' ? 'Virtual Assistant' : 'Client'; const userTypeText = userType === 'va' ? 'VA' : 'CLIENT';
const emailData = {
to: email,
subject: `${userTypeText} - Password Reset Request`,
html: this.generateResetEmailTemplate(resetLink, userTypeText),
};
await this.emailService.sendEmail(emailData); await axios.post(`${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/forgot-password`, {
logger.info(`Password reset email sent to ${userType}: ${email}`); data: {
email: email,
url: resetLink,
userType: userTypeText
}
});
} catch (error) { } catch (error) {
logger.error(`PasswordReset Service - sendResetEmail error for ${userType}: ${error.message}`); logger.error(`PasswordReset Service - sendResetEmail error for ${userType}: ${error.message}`);
@ -186,7 +186,7 @@ class PasswordResetService {
async sendPasswordChangedEmail(email, userType) { async sendPasswordChangedEmail(email, userType) {
try { try {
const userTypeText = userType === 'va' ? 'Virtual Assistant' : 'Client'; const userTypeText = userType === 'va' ? 'Virtual Assistant' : 'Client';
const emailData = { const emailData = {
to: email, to: email,
subject: `${userTypeText} - Password Changed Successfully`, subject: `${userTypeText} - Password Changed Successfully`,

View File

@ -326,6 +326,16 @@
vaTableBody.innerHTML = allVAs.map(va => createVATableRow(va)).join(''); vaTableBody.innerHTML = allVAs.map(va => createVATableRow(va)).join('');
vaTableBody.style.display = ''; vaTableBody.style.display = '';
vaEmptyState.style.display = 'none'; vaEmptyState.style.display = 'none';
// Add event listeners to view links
// document.querySelectorAll('.view-link').forEach(link => {
// link.addEventListener('click', function(e) {
// e.preventDefault();
// const vaId = this.getAttribute('data-va-id');
// // You can update this link as needed
// window.location.href = `/client/virtual-assistants/${vaId}`;
// });
// });
} else { } else {
vaTableBody.innerHTML = ''; vaTableBody.innerHTML = '';
vaTableBody.style.display = 'none'; vaTableBody.style.display = 'none';

View File

@ -213,7 +213,7 @@
<body> <body>
<div class="logo"> <div class="logo">
<img src="/public/images/logo-white-vertical.svg" alt="DocVA Logo" /> <img id="logo-img" src="/public/images/logo-white-vertical.svg" alt="DocVA Logo" />
</div> </div>
<div class="login-card"> <div class="login-card">
@ -224,7 +224,7 @@
<input type="password" name="password" placeholder="Enter your password *" required /> <input type="password" name="password" placeholder="Enter your password *" required />
<a href="/client/forgot-password" class="forgot">Forgot password?</a> <a href="/client/forgot-password" class="forgot">Forgot password?</a>
<button type="submit" class="btn btn-primary">🔒 Log in using password</button> <button type="submit" class="btn btn-primary">Log in using password</button>
<!-- <div class="divider">OR</div> <!-- <div class="divider">OR</div>
@ -236,6 +236,10 @@
const root = document.documentElement; const root = document.documentElement;
function setTheme(theme) { function setTheme(theme) {
document.getElementById("logo-img").src =
theme === "dark"
? "/public/images/logo-white-vertical.svg"
: "/public/images/logo-colored-vertical.svg";
root.setAttribute("data-theme", theme); root.setAttribute("data-theme", theme);
} }
@ -276,7 +280,7 @@
if (!res.ok) { if (!res.ok) {
throw new Error(data.message || "Login failed"); throw new Error(data.message || "Login failed");
} }
if (data.success) { if (data.success) {
// 🟢 Save token // 🟢 Save token
localStorage.setItem("knoptpq", data.accessToken); localStorage.setItem("knoptpq", data.accessToken);

View File

@ -139,7 +139,7 @@
<script> <script>
const osButtons = document.querySelectorAll(".os-btn"); const osButtons = document.querySelectorAll(".os-btn");
const downloadBtn = document.getElementById("downloadBtn"); const downloadBtn = document.getElementById("downloadBtn");
const version = "<%= version || '3.2.1' %>"; const version = "<%= version || '1.1.1' %>";
let selectedOS = "windows"; let selectedOS = "windows";
@ -171,7 +171,13 @@
// -------- Download -------- // -------- Download --------
downloadBtn.addEventListener("click", () => { downloadBtn.addEventListener("click", () => {
window.location.href = `/download-file?os=${selectedOS}`;
const link = document.createElement("a");
link.href = `/public/builds/build-1.1.1.1/windows/VA Tracker-Setup-1.1.1.exe`;
link.setAttribute("download", "");
link.click()
// window.location.href = ``;
}); });
</script> </script>

View File

@ -107,7 +107,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
<input type="email" id="email" class="input-field" placeholder=" " required> <input type="email" id="email" class="input-field" placeholder=" " value="<%= email %>" readonly disabled required>
<label for="email" class="input-label">Email *</label> <label for="email" class="input-label">Email *</label>
</div> </div>
</div> </div>

View File

@ -174,7 +174,7 @@
<form id="account-form"> <form id="account-form">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
<input type="email" id="email" class="input-field" placeholder=" " required autocomplete="username"> <input type="email" id="email" class="input-field" placeholder=" " value="<%= email %>" readonly disabled required>
<label for="email" class="input-label">Email Address *</label> <label for="email" class="input-label">Email Address *</label>
</div> </div>
</div> </div>