Merge branch 'master' of http://git.hicglobalsolutions.in/BhawanSingh/va-tracker-middleware
This commit is contained in:
commit
e1fc8d9d13
|
|
@ -71,7 +71,7 @@ async function va_login_client(req, res) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("va_register error:", error);
|
console.error("va_client_login_register error:", error);
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,58 @@
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LoggedModel } from "../../../models/vaModel.js";
|
import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
|
// function to close open work tracks and create logout
|
||||||
|
async function closePreviousSession(vaId, lastLog) {
|
||||||
|
try {
|
||||||
|
const currentTime = new Date();
|
||||||
|
// Find all open work tracks for this VA and project (where to is not set or totalTime is 0)
|
||||||
|
const openWorkTracks = await WorkTrackModel.find({
|
||||||
|
vaId,
|
||||||
|
logId: lastLog._id,
|
||||||
|
$or: [
|
||||||
|
{ to: { $exists: false } },
|
||||||
|
{ totalTime: 0 },
|
||||||
|
{ to: null }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(`Found ${openWorkTracks.length} open work tracks to close`);
|
||||||
|
|
||||||
|
// Close all open work tracks
|
||||||
|
for (const workTrack of openWorkTracks) {
|
||||||
|
const fromTime = new Date(workTrack.from);
|
||||||
|
const totalTimeMs = currentTime - fromTime;
|
||||||
|
|
||||||
|
// Update the work track with end time and total time
|
||||||
|
workTrack.to = currentTime;
|
||||||
|
workTrack.totalTime = totalTimeMs;
|
||||||
|
await workTrack.save();
|
||||||
|
|
||||||
|
// console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a logout log entry
|
||||||
|
const logoutLog = await LoggedModel.create({
|
||||||
|
loggedType: "LOGOUT",
|
||||||
|
vaId,
|
||||||
|
vaEmail: lastLog.vaEmail, // Use email from previous log
|
||||||
|
projectId: lastLog.projectId,
|
||||||
|
time: currentTime // Use same timestamp for consistent logging
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(`Created logout log: ${logoutLog._id}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
closedWorkTracks: openWorkTracks.length,
|
||||||
|
logoutLogId: logoutLog._id
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error closing previous session:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Login VA
|
// Login VA
|
||||||
async function va_login_callback(req, res) {
|
async function va_login_callback(req, res) {
|
||||||
|
|
@ -32,36 +84,22 @@ async function va_login_callback(req, res) {
|
||||||
return res.status(401).json({ success: false, message: "Invalid password" });
|
return res.status(401).json({ success: false, message: "Invalid password" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// also on main login when user try to login into app then logout project and workModel track then allow login on same timestamp
|
||||||
|
|
||||||
// Check if VA has any active sessions (LOGIN without LOGOUT)
|
// Check if VA has any active sessions (LOGIN without LOGOUT)
|
||||||
const activeSessions = await LoggedModel.find({
|
const activeSessions = await LoggedModel.find({
|
||||||
vaId: user._id,
|
vaId: user._id
|
||||||
loggedType: "LOGIN"
|
|
||||||
}).sort({ time: -1 });
|
}).sort({ time: -1 });
|
||||||
|
|
||||||
|
let closeResult = [];
|
||||||
|
|
||||||
if (activeSessions.length > 0) {
|
if (activeSessions.length > 0) {
|
||||||
// Get the latest login to check if there's no corresponding logout
|
// Get the latest login to check if there's no corresponding logout
|
||||||
const latestLogin = activeSessions[0];
|
const latestLog = activeSessions[0];
|
||||||
|
closeResult = await closePreviousSession(user._id, latestLog);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there's a logout after this login
|
|
||||||
const correspondingLogout = await LoggedModel.findOne({
|
|
||||||
vaId: user._id,
|
|
||||||
loggedType: "LOGOUT",
|
|
||||||
time: { $gt: latestLogin.time }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!correspondingLogout) {
|
|
||||||
// VA is still logged in to a project
|
|
||||||
const projectInfo = await LoggedModel.findById(latestLogin._id)
|
|
||||||
.populate('projectId', 'name')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: "You are still logged in to a project. Please logout first before logging in again."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate JWT accessToken with 24h expiry
|
// Generate JWT accessToken with 24h expiry
|
||||||
const accessToken = jwt.sign(
|
const accessToken = jwt.sign(
|
||||||
{
|
{
|
||||||
|
|
@ -88,18 +126,40 @@ async function va_login_callback(req, res) {
|
||||||
$set: { accessToken, refreshToken }
|
$set: { accessToken, refreshToken }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let responseData = [];
|
||||||
|
|
||||||
|
if (closeResult) {
|
||||||
|
responseData = {
|
||||||
|
success: true,
|
||||||
|
message: "Login Successfull",
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
user: {
|
||||||
|
id: user._id,
|
||||||
|
name: user.userDetails.firstName,
|
||||||
|
email: user.userDetails.email,
|
||||||
|
},
|
||||||
|
previousSession: {
|
||||||
|
closedWorkTracks: closeResult.closedWorkTracks,
|
||||||
|
logoutLogId: closeResult.logoutLogId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseData = {
|
||||||
|
success: true,
|
||||||
|
message: "Login Successfull",
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
user: {
|
||||||
|
id: user._id,
|
||||||
|
name: user.userDetails.firstName,
|
||||||
|
email: user.userDetails.email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return accessToken and user info
|
// Return accessToken and user info
|
||||||
return res.status(200).json({
|
return res.status(200).json(responseData);
|
||||||
success: true,
|
|
||||||
message: "Login Successfull",
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
user: {
|
|
||||||
id: user._id,
|
|
||||||
name: user.userDetails.firstName,
|
|
||||||
email: user.userDetails.email,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("va_register error:", error);
|
console.error("va_register error:", error);
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,91 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { WorkTrackModel, VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js";
|
import { WorkTrackModel, VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
|
// Helper function to get start and end of day
|
||||||
|
function getDayRange(date = new Date()) {
|
||||||
|
const start = new Date(date);
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const end = new Date(date);
|
||||||
|
end.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatHoursToReadable(hours) {
|
||||||
|
const wholeHours = Math.floor(hours);
|
||||||
|
const decimalMinutes = (hours - wholeHours) * 60;
|
||||||
|
const wholeMinutes = Math.round(decimalMinutes);
|
||||||
|
|
||||||
|
if (wholeHours > 0 && wholeMinutes > 0) {
|
||||||
|
return `${wholeHours}h ${wholeMinutes}m`;
|
||||||
|
} else if (wholeHours > 0) {
|
||||||
|
return `${wholeHours}h`;
|
||||||
|
} else {
|
||||||
|
return `${wholeMinutes}m`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check daily hours and create logout log if needed
|
||||||
|
async function checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, currentSessionId = null, additionalTimeMs = 0) {
|
||||||
|
try {
|
||||||
|
const today = new Date();
|
||||||
|
const { start, end } = getDayRange(today);
|
||||||
|
|
||||||
|
// Get today's work entries for this VA and project
|
||||||
|
// Exclude the current session if it's already in the database
|
||||||
|
const query = {
|
||||||
|
vaId,
|
||||||
|
projectId,
|
||||||
|
from: { $gte: start, $lte: end }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentSessionId) {
|
||||||
|
query._id = { $ne: currentSessionId };
|
||||||
|
}
|
||||||
|
|
||||||
|
const todayWorkEntries = await WorkTrackModel.find(query);
|
||||||
|
|
||||||
|
// Calculate total hours worked today
|
||||||
|
let totalTodayMs = todayWorkEntries.reduce((total, entry) => {
|
||||||
|
return total + (entry.totalTime || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Add the additional time from current session if provided
|
||||||
|
totalTodayMs += additionalTimeMs;
|
||||||
|
|
||||||
|
const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
|
||||||
|
|
||||||
|
// Check if daily dedicated hours are completed
|
||||||
|
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
|
||||||
|
// Create automatic logout log
|
||||||
|
const logoutLog = await LoggedModel.create({
|
||||||
|
loggedType: "LOGOUT",
|
||||||
|
vaId,
|
||||||
|
vaEmail,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldLogout: true,
|
||||||
|
totalTodayHours: totalTodayHours,
|
||||||
|
dedicatedHours: project.dedicatedHours,
|
||||||
|
logoutLogId: logoutLog._id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldLogout: false,
|
||||||
|
totalTodayHours: totalTodayHours,
|
||||||
|
dedicatedHours: project.dedicatedHours
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in checkDailyHoursAndLogout:", error);
|
||||||
|
return { shouldLogout: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Getting logged hours in a specific project of VA
|
// Getting logged hours in a specific project of VA
|
||||||
async function va_log_track_work_callback(req, res) {
|
async function va_log_track_work_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -50,6 +135,16 @@ async function va_log_track_work_callback(req, res) {
|
||||||
|
|
||||||
// Handle POST → create entry
|
// Handle POST → create entry
|
||||||
if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
|
const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project);
|
||||||
|
|
||||||
|
if (dailyCheck.shouldLogout) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: `Cannot start work - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`,
|
||||||
|
autoLogout: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const createdTrack = await WorkTrackModel.create({
|
const createdTrack = await WorkTrackModel.create({
|
||||||
vaId,
|
vaId,
|
||||||
projectId,
|
projectId,
|
||||||
|
|
@ -63,6 +158,11 @@ async function va_log_track_work_callback(req, res) {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Work tracking started",
|
message: "Work tracking started",
|
||||||
data: createdTrack,
|
data: createdTrack,
|
||||||
|
dailyHours: {
|
||||||
|
todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours),
|
||||||
|
dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours),
|
||||||
|
remaining: formatHoursToReadable(dailyCheck.dedicatedHours - dailyCheck.totalTodayHours)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,11 +189,14 @@ async function va_log_track_work_callback(req, res) {
|
||||||
|
|
||||||
const formattedTotalTime = `${hours}h ${minutes}m ${seconds}s`;
|
const formattedTotalTime = `${hours}h ${minutes}m ${seconds}s`;
|
||||||
|
|
||||||
|
// Check if daily dedicated hours are completed after this work session
|
||||||
|
const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, id, totalTimeMs);
|
||||||
|
|
||||||
workEntry.to = toTime;
|
workEntry.to = toTime;
|
||||||
workEntry.totalTime = totalTimeMs; // store as number of minutes
|
workEntry.totalTime = totalTimeMs; // store as number of minutes
|
||||||
await workEntry.save();
|
await workEntry.save();
|
||||||
|
|
||||||
return res.status(200).json({
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Work tracking stopped",
|
message: "Work tracking stopped",
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -101,9 +204,24 @@ async function va_log_track_work_callback(req, res) {
|
||||||
from: workEntry.from,
|
from: workEntry.from,
|
||||||
to: workEntry.to,
|
to: workEntry.to,
|
||||||
formattedTotalTime,
|
formattedTotalTime,
|
||||||
totalTimeMs
|
totalTimeMs,
|
||||||
|
sessionHours: totalTimeMs / (1000 * 60 * 60)
|
||||||
},
|
},
|
||||||
});
|
dailyHours: {
|
||||||
|
todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours),
|
||||||
|
dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours),
|
||||||
|
remaining: formatHoursToReadable(Math.max(0, dailyCheck.dedicatedHours - dailyCheck.totalTodayHours))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If auto-logout was triggered, add that info to response
|
||||||
|
if (dailyCheck.shouldLogout) {
|
||||||
|
response.autoLogout = true;
|
||||||
|
response.message = `Work tracking stopped. Auto-logout created - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`;
|
||||||
|
response.logoutLogId = dailyCheck.logoutLogId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for unsupported methods
|
// Fallback for unsupported methods
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,198 @@
|
||||||
import { LoggedModel } from "../../../models/vaModel.js";
|
import { LoggedModel, WorkTrackModel, ProjectModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
|
// Helper function to get start and end of day
|
||||||
|
function getDayRange(date = new Date()) {
|
||||||
|
const start = new Date(date);
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const end = new Date(date);
|
||||||
|
end.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatHoursToReadable(hours) {
|
||||||
|
const wholeHours = Math.floor(hours);
|
||||||
|
const decimalMinutes = (hours - wholeHours) * 60;
|
||||||
|
const wholeMinutes = Math.round(decimalMinutes);
|
||||||
|
|
||||||
|
if (wholeHours > 0 && wholeMinutes > 0) {
|
||||||
|
return `${wholeHours}h ${wholeMinutes}m`;
|
||||||
|
} else if (wholeHours > 0) {
|
||||||
|
return `${wholeHours}h`;
|
||||||
|
} else {
|
||||||
|
return `${wholeMinutes}m`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function to close open work tracks and create logout
|
||||||
|
async function closePreviousSession(vaId, lastLog) {
|
||||||
|
try {
|
||||||
|
const currentTime = new Date();
|
||||||
|
|
||||||
|
// Find all open work tracks for this VA and project (where to is not set or totalTime is 0)
|
||||||
|
const openWorkTracks = await WorkTrackModel.find({
|
||||||
|
vaId,
|
||||||
|
logId: lastLog._id,
|
||||||
|
$or: [
|
||||||
|
{ to: { $exists: false } },
|
||||||
|
{ totalTime: 0 },
|
||||||
|
{ to: null }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(`Found ${openWorkTracks.length} open work tracks to close`);
|
||||||
|
|
||||||
|
// Close all open work tracks
|
||||||
|
for (const workTrack of openWorkTracks) {
|
||||||
|
const fromTime = new Date(workTrack.from);
|
||||||
|
const totalTimeMs = currentTime - fromTime;
|
||||||
|
|
||||||
|
// Update the work track with end time and total time
|
||||||
|
workTrack.to = currentTime;
|
||||||
|
workTrack.totalTime = totalTimeMs;
|
||||||
|
await workTrack.save();
|
||||||
|
|
||||||
|
// console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a logout log entry
|
||||||
|
const logoutLog = await LoggedModel.create({
|
||||||
|
loggedType: "LOGOUT",
|
||||||
|
vaId,
|
||||||
|
vaEmail: lastLog.vaEmail, // Use email from previous log
|
||||||
|
projectId: lastLog.projectId,
|
||||||
|
time: currentTime // Use same timestamp for consistent logging
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(`Created logout log: ${logoutLog._id}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
closedWorkTracks: openWorkTracks.length,
|
||||||
|
logoutLogId: logoutLog._id
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error closing previous session:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Getting logged hours in a specific project of VA
|
// Getting logged hours in a specific project of VA
|
||||||
async function va_project_log_callback(req, res) {
|
async function va_project_log_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
const payload = req.body || {};
|
const payload = req.body || {};
|
||||||
const { vaId, vaEmail, projectId, type } = payload;
|
const { vaId, vaEmail, projectId, type } = payload;
|
||||||
|
|
||||||
if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props");
|
if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props");
|
||||||
const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
|
|
||||||
|
|
||||||
return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id });
|
const project = await ProjectModel.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
return res.status(400).json({ success: false, message: "Project not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// For LOGIN requests, check daily hours
|
||||||
|
if (type === "LOGIN") {
|
||||||
|
const { start, end } = getDayRange();
|
||||||
|
// Get today's total hours
|
||||||
|
const todayWorkEntries = await WorkTrackModel.find({
|
||||||
|
vaId,
|
||||||
|
projectId,
|
||||||
|
from: { $gte: start, $lte: end }
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalTodayMs = todayWorkEntries.reduce((total, entry) => {
|
||||||
|
return total + (entry.totalTime || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
|
||||||
|
|
||||||
|
// Check if daily hours are already completed
|
||||||
|
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: `Cannot login - daily hours already completed (${formatHoursToReadable(totalTodayHours)} / ${formatHoursToReadable(project.dedicatedHours)})`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check last log entry
|
||||||
|
const lastLog = await LoggedModel.findOne({ vaId })
|
||||||
|
.sort({ time: -1 })
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
// Prevent login if last action was login (not logout)
|
||||||
|
if (lastLog && lastLog.loggedType === "LOGIN") {
|
||||||
|
|
||||||
|
// check last workModel login time and make a logout request then create login
|
||||||
|
// console.log(`User already logged in. Closing previous session...`);
|
||||||
|
const closeResult = await closePreviousSession(vaId, lastLog);
|
||||||
|
|
||||||
|
// console.log(`Previous session closed: ${closeResult.closedWorkTracks} work tracks, logout log: ${closeResult.logoutLogId}`);
|
||||||
|
|
||||||
|
// Now proceed to create new login
|
||||||
|
const createdLog = await LoggedModel.create({
|
||||||
|
loggedType: "LOGIN",
|
||||||
|
vaId,
|
||||||
|
vaEmail,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(201).send({
|
||||||
|
success: true,
|
||||||
|
message: `Login Successful (previous session closed)`,
|
||||||
|
logId: createdLog._id,
|
||||||
|
previousSession: {
|
||||||
|
closedWorkTracks: closeResult.closedWorkTracks,
|
||||||
|
logoutLogId: closeResult.logoutLogId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
|
||||||
|
// console.log(createdLog)
|
||||||
|
try {
|
||||||
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`;
|
||||||
|
const sfData = {
|
||||||
|
"_id": createdLog._id,
|
||||||
|
"loggedType": type,
|
||||||
|
"vaId": vaId,
|
||||||
|
"vaEmail": vaEmail,
|
||||||
|
"projectId": projectId,
|
||||||
|
"time": createdLog.time,
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(salesforceUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
// Add any required headers for Salesforce authentication
|
||||||
|
// 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
|
||||||
|
},
|
||||||
|
body: JSON.stringify(sfData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
// console.log("✅ Successfully synced VA data to Salesforce:", result);
|
||||||
|
|
||||||
|
res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._id, SyncSalesforce: result });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to sync with Salesforce:", error.message);
|
||||||
|
console.log(error)
|
||||||
|
|
||||||
|
// Create a custom error to distinguish Salesforce errors
|
||||||
|
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
||||||
|
salesforceError.isSalesforceError = true;
|
||||||
|
throw salesforceError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,21 @@ function formatHMS(ms) {
|
||||||
return `${h}h ${m}m ${s}s`;
|
return `${h}h ${m}m ${s}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatHoursToReadable(hours) {
|
||||||
|
const wholeHours = Math.floor(hours);
|
||||||
|
const decimalMinutes = (hours - wholeHours) * 60;
|
||||||
|
const wholeMinutes = Math.round(decimalMinutes);
|
||||||
|
|
||||||
|
if (wholeHours > 0 && wholeMinutes > 0) {
|
||||||
|
return `${wholeHours}h ${wholeMinutes}m`;
|
||||||
|
} else if (wholeHours > 0) {
|
||||||
|
return `${wholeHours}h`;
|
||||||
|
} else {
|
||||||
|
return `${wholeMinutes}m`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function va_projects_log_list_function(req, res) {
|
async function va_projects_log_list_function(req, res) {
|
||||||
try {
|
try {
|
||||||
const { projectId, from, to } = req.params;
|
const { projectId, from, to } = req.params;
|
||||||
|
|
@ -101,7 +116,9 @@ async function va_projects_log_list_function(req, res) {
|
||||||
activityStatus,
|
activityStatus,
|
||||||
totalWorkTime: formatHMS(totalWorkMs),
|
totalWorkTime: formatHMS(totalWorkMs),
|
||||||
totalWorkMs,
|
totalWorkMs,
|
||||||
totalLogs: logs.length
|
dedicatedHours: formatHoursToReadable(project?.dedicatedHours),
|
||||||
|
totalLogs: logs.length,
|
||||||
|
status: project?.status
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ const projectSchema = new Schema({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dedicatedHours: {
|
dedicatedHours: {
|
||||||
type: Number,
|
type: String,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
|
|
@ -311,8 +311,8 @@ const vaWorkTrackSchema = new Schema({
|
||||||
ref: "Project",
|
ref: "Project",
|
||||||
},
|
},
|
||||||
logId: {
|
logId: {
|
||||||
type: String,
|
type: Types.ObjectId,
|
||||||
ref: "Logged",
|
ref: "TrackLog",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,32 @@ import cors from 'cors';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
|
||||||
import apiRoutes from './routes/index.js'
|
import apiRoutes from './routes/index.js'
|
||||||
|
|
||||||
|
|
||||||
import { render_reset_form } from "./controllers/passwordReset.controllers.js";
|
import { render_reset_form } from "./controllers/passwordReset.controllers.js";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
const app = express();
|
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);
|
||||||
|
|
||||||
|
|
@ -70,10 +86,13 @@ app.get("/download-file", (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.get('/varegister/:sf_token?', (req, res) => {
|
app.get('/varegister/:sf_token?', (req, res) => {
|
||||||
const { sf_token } = req.params;
|
const { sf_token } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
|
||||||
|
const { sf_token } = req.params;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|
@ -95,6 +114,9 @@ app.get('/varegister/:sf_token?', (req, res) => {
|
||||||
|
|
||||||
app.get('/clientregister/:sf_token?', (req, res) => {
|
app.get('/clientregister/:sf_token?', (req, res) => {
|
||||||
|
|
||||||
|
// portalId: RECORDid => 8990928342309
|
||||||
|
// email
|
||||||
|
|
||||||
// const { sf_token } = useParams();
|
// const { sf_token } = useParams();
|
||||||
|
|
||||||
res.render('va_client_register', { title: "Client Registration" });
|
res.render('va_client_register', { title: "Client Registration" });
|
||||||
|
|
@ -166,6 +188,27 @@ app.get('/client/virtual-assistants', async (req, res) => {
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
app.get("/live/:projectId/:vaId", async (req, res) => {
|
||||||
|
const { projectId, vaId } = req.params;
|
||||||
|
|
||||||
|
if (!projectId || !vaId) {
|
||||||
|
return res.status(400).send({ "message": "Page not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let clientId = null;
|
||||||
|
try {
|
||||||
|
const clientRes = await ProjectModel.findById(projectId);
|
||||||
|
if (clientRes) {
|
||||||
|
clientId = clientRes.clientId.toString();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching client for project:", err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clientId) return res.status(400).send({ "message": "Page not found" });
|
||||||
|
|
||||||
|
res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId })
|
||||||
app.get("/live/:projectId/:vaId", async (req, res) => {
|
app.get("/live/:projectId/:vaId", async (req, res) => {
|
||||||
const { projectId, vaId } = req.params;
|
const { projectId, vaId } = req.params;
|
||||||
|
|
||||||
|
|
@ -403,12 +446,34 @@ io.on("connection", (socket) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/client/login", (req, res) => {
|
|
||||||
res.render("client_login")
|
|
||||||
})
|
// io.on('connection', (socket) => {
|
||||||
app.get("/client/forgot-password", (req, res) => {
|
|
||||||
res.render("forgot_password")
|
// socket.on("sendImage", (base64String) => {
|
||||||
})
|
// io.emit("receiveImage", base64String);
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
// socket.on('disconnect', () => {
|
||||||
|
// console.log('a user Disconnected');
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
// Express App Listening
|
||||||
|
// app.listen(PORT, () => {
|
||||||
|
// mongoose
|
||||||
|
// .connect(
|
||||||
|
// process.env.MONGO_URI,
|
||||||
|
// )
|
||||||
|
// .then(() => console.log("mongodb connected"))
|
||||||
|
// .catch((err) => {
|
||||||
|
// console.log(err.message);
|
||||||
|
// process.exit(1);
|
||||||
|
// });
|
||||||
|
// console.log(`Express started on http://localhost:${PORT}`);
|
||||||
|
// });
|
||||||
|
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
res.status(404).render("not_found", {
|
res.status(404).render("not_found", {
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ class SalesforceService {
|
||||||
const { userDetails } = clientData;
|
const { userDetails } = clientData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: _id,
|
|
||||||
userDetails: {
|
userDetails: {
|
||||||
|
_id: _id,
|
||||||
name: userDetails.name,
|
name: userDetails.name,
|
||||||
email: userDetails.email.toLowerCase(),
|
email: userDetails.email.toLowerCase(),
|
||||||
address: userDetails.address || null,
|
address: userDetails.address || null,
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@ class VaClientService {
|
||||||
|
|
||||||
// Sanitize and prepare data
|
// Sanitize and prepare data
|
||||||
const clientData = this.sanitizeClientData(data);
|
const clientData = this.sanitizeClientData(data);
|
||||||
|
|
||||||
// Send data to Salesforce first
|
|
||||||
await this.salesforceService.registerClientInSalesforce(data, clientData._id);
|
|
||||||
|
|
||||||
const response = await this.clientRepository.create(clientData);
|
const response = await this.clientRepository.create(clientData);
|
||||||
|
|
||||||
|
if(response) {
|
||||||
|
await this.salesforceService.registerClientInSalesforce(data, response._id);
|
||||||
|
}
|
||||||
|
// Send data to Salesforce first
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Client Service - registerClient error: " + error.message);
|
logger.error("Client Service - registerClient error: " + error.message);
|
||||||
|
|
|
||||||
|
|
@ -276,11 +276,11 @@
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(data.message || "Login failed");
|
throw new Error(data.message || "Login failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.accessToken) {
|
if (data.success) {
|
||||||
// 🟢 Save token
|
// 🟢 Save token
|
||||||
localStorage.setItem("knoptpq", data.accessToken);
|
localStorage.setItem("knoptpq", data.accessToken);
|
||||||
localStorage.setItem("refreshToken", data.refreshToken);
|
// localStorage.setItem("refreshToken", data.refreshToken);
|
||||||
|
|
||||||
// 🟢 Redirect
|
// 🟢 Redirect
|
||||||
window.location.href = "/client/dashboard";
|
window.location.href = "/client/dashboard";
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Live Screen Feed</title>
|
<title>Live Screen Feed</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -163,8 +163,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Mobile Toggle Button -->
|
||||||
<!-- Mobile Toggle Button -->
|
|
||||||
<div class="mobile-toggle" id="toggleBtn">
|
<div class="mobile-toggle" id="toggleBtn">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue