diff --git a/src/controllers/va.controller.js b/src/controllers/va.controller.js index 4555cd7..43dcdd4 100644 --- a/src/controllers/va.controller.js +++ b/src/controllers/va.controller.js @@ -2,6 +2,22 @@ import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../models/vaModel.js"; + + +async function logVaTime(req, res) { + try { + const payload = req.body || {}; + const { vaId, vaEmail, projectId, type } = payload; + + if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props"); + await LoggedModel.insert({ loggedType: type, vaId, vaEmail, projectId }) + + return res.status(201).send({ success: true, message: `${type} Successfully` }); + } catch (error) { + return res.status(500).send("Internal Server Error"); + } +} + // Getting all active VA's and their details async function va_callback(req, res) { try { @@ -18,7 +34,7 @@ async function va_callback(req, res) { return res.status(200).json({ success: true, count: 0, data: [] }); } - + const vas = await VAModel.find({ _id: { $in: vaIds } }).lean().exec(); @@ -41,18 +57,18 @@ async function va_register(req, res) { try { const payload = req.body ? JSON.parse(req.body) : {}; const { userDetails } = payload; - + if (!userDetails || !userDetails.email || !userDetails.firstName || !userDetails.password) { return res.status(400).json({ success: false, message: "Missing required fields" }); } - + if (userDetails.password !== userDetails.cPassword) { return res.status(400).json({ success: false, message: "password not matching" }); } const saltRounds = 10; const hashedPassword = await bcrypt.hash(userDetails.password, saltRounds); - + // sanitize/construct allowed fields const toCreate = { userDetails: { @@ -89,7 +105,7 @@ async function va_login_callback(req, res) { // const payload = req.body ? JSON.parse(req.body) : {}; const payload = req.body || {}; const { userDetails } = payload; - + if (!userDetails || !userDetails.email || !userDetails.password) { return res.status(400).json({ success: false, message: "Please provide email and password" }); } @@ -113,7 +129,7 @@ async function va_login_callback(req, res) { return res.status(401).json({ success: false, message: "Invalid password" }); } - // Generate JWT accessToken with 24h expiry + // Generate JWT accessToken with 24h expiry const accessToken = jwt.sign( { id: user._id, @@ -124,7 +140,7 @@ async function va_login_callback(req, res) { { expiresIn: "24h" } ); - // Generate JWT refreshToken with 24h expiry + // Generate JWT refreshToken with 24h expiry const refreshToken = jwt.sign( { id: user._id, @@ -135,10 +151,14 @@ async function va_login_callback(req, res) { { expiresIn: "30d" } ); + await VAModel.updateOne({ "userDetails.email": userDetails.email }, { + $set: { accessToken, refreshToken } + }) + // Return accessToken and user info return res.status(200).json({ success: true, - message: "Login successful", + message: "Login Successfull", accessToken, refreshToken, user: { @@ -152,6 +172,33 @@ async function va_login_callback(req, res) { return res.status(500).json({ success: false, message: "Internal Server Error" }); } } +async function va_logout_callback(req, res) { + try { + // const payload = req.body ? JSON.parse(req.body) : {}; + const vaId = req.body.vaId || {}; + const vaEmail = req.body.vaEmail || {}; + if (!vaId || !vaEmail) return res.status(400).json({ success: false, message: "Invalid Credentials" }); + + // Find user by email + const user = await VAModel.findOne({ "userDetails.email": vaEmail }); + if (!user) { + return res.status(404).json({ success: false, message: "Invalid credentials" }); + } + + await VAModel.updateOne({ "userDetails.email": userDetails.email }, { + $set: { accessToken: null, refreshToken: null } + }) + + // Return accessToken and user info + return res.status(200).json({ + success: true, + message: "Log Out Successfull", + }); + } catch (error) { + console.error("va_register error:", error); + return res.status(500).json({ success: false, message: "Internal Server Error" }); + } +} // Refresh Token VA async function va_refresh_token_callback(req, res) { @@ -164,7 +211,7 @@ async function va_refresh_token_callback(req, res) { message: "Refresh token missing", }); } - + // 🔍 Verify refresh token jwt.verify( refreshToken, @@ -358,35 +405,110 @@ async function va_clients_projects_callback(req, res) { } } -// Getting VA log between specific dates with optional screenshot async function va_log_callback(req, res) { try { - const { id, from, to, screentShot } = req.params; - return res.status(200).json({ + const { projectId, from, to } = req.params; + const { vaId } = req.body; + + if (!projectId) return res.status(400).json({ success: false, message: "Missing Props" }) + + const project = await ProjectModel.findById(projectId); + + if (!project) return res.status(400).json({ success: false, message: "Project not found" }); + + // Default range + const fromDate = from && from !== "undefined" ? new Date(from) : new Date(project.startDate || "2000-01-01"); + const toDate = to && to !== "undefined" ? new Date(to) : new Date(); + + // If from and to are the same day + if (from === to) { + fromDate.setHours(0, 0, 0, 0); + toDate.setHours(23, 59, 59, 999); + } else { + // Otherwise use them as a normal range + toDate.setHours(23, 59, 59, 999); + } + + const query = { + vaId: vaId, + projectId, + time: { + $gte: fromDate, + $lte: toDate + } + }; + + + const logs = await LoggedModel.find(query).sort({ time: 1 }); + + if (!logs.length) { + return res.json({ success: true, message: "No logs found", totalWork: "0h 0m" }); + } + + // Find first LOGIN and last LOGOUT + const firstLogin = logs.find((l) => l.loggedType === "LOGIN"); + const lastLogout = [...logs].reverse().find((l) => l.loggedType === "LOGOUT"); + + if (!firstLogin || !lastLogout) { + return res.json({ + success: true, + message: "Incomplete log data (missing LOGIN or LOGOUT)", + totalWork: "0h 0m", + firstLogin, + lastLogout, + }); + } + + // Calculate duration + const diffMs = new Date(lastLogout.time) - new Date(firstLogin.time); + const totalMinutes = Math.floor(diffMs / (1000 * 60)); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + + res.json({ success: true, - message: "VA Log Callback", - id, - from, - to, - screentShot + vaId, + projectId, + firstLogin: firstLogin.time, + lastLogout: lastLogout.time, + logs: logs, + totalTimeLogged: `${hours}h ${minutes}m`, + hoursLogged: hours, + minutesLogged: minutes, }); - } catch (error) { - return res.status(500).json({ success: false, message: "Internal Server Error" }); + + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: err.message }); } -} +}; // Getting logged hours in a specific project of VA async function va_project_log_callback(req, res) { try { - const { id, projectID } = req.params; - return res.status(200).json({ - success: true, - message: "VA Project Log Callback", - id, - projectID - }); + const payload = req.body || {}; + const { vaId, vaEmail, projectId, type } = payload; + + if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props"); + await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId }) + + return res.status(201).send({ success: true, message: `${type} Successfully` }); } catch (error) { - return res.status(500).json({ success: false, message: "Internal Server Error" }); + return res.status(500).send("Internal Server Error"); + } +} + +async function va_time_logged_on_proj(req, res) { + try { + const payload = req.body || {}; + const { vaId, vaEmail, projectId, type } = payload; + + if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props"); + await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId }) + + return res.status(201).send({ success: true, message: `${type} Successfully` }); + } catch (error) { + return res.status(500).send("Internal Server Error"); } } @@ -477,6 +599,7 @@ async function va_filter_search(req, res) { export { + logVaTime, va_callback, va_username_callback, va_clients_callback, @@ -487,5 +610,6 @@ export { va_register, va_projects_callback, va_login_callback, - va_refresh_token_callback + va_logout_callback, + va_refresh_token_callback, }; diff --git a/src/middlewares/auth.middleware.js b/src/middlewares/auth.middleware.js index c38433b..815f65e 100644 --- a/src/middlewares/auth.middleware.js +++ b/src/middlewares/auth.middleware.js @@ -1,76 +1,36 @@ -import AuthRepository from "../repository/auth.repository.js"; -import AuthService from "../services/auth.service.js"; - -import TemplateRepository from "../repository/template.repository.js"; -import TemplateService from "../services/template.service.js"; - -import httpReqRefToken from "../httpReq/refreshToken.httpReq.js"; -import httpReqValidateAuth from "../httpReq/validateAuth.httpReq.js"; -import { decryptCryptr, encryptCryptr } from "../helper/cryptrEncDec.js"; -import logger from "../config/logger.js"; - -const authService = new AuthService(new AuthRepository()); -const templateService = new TemplateService(new TemplateRepository()); - +import jwt from "jsonwebtoken"; +import { VAModel } from "../models/vaModel.js"; async function authValidator(req, res, next) { try { - logger.info("AuthValidator - middleware is running ...") - logger.info("Process ID", process.pid) - let templateId = null; - let orgId; - if ('template' in req.body && 'org_auth_id' in req.body.template && req.body.template['org_auth_id'] != null) { + const accessToken = req.headers.token; + if (!accessToken) return res.status(401).json({ success: false, message: "Not Authorized" }); - orgId = req.body.template.org_auth_id; - } else { - if ('templateId' in req.body) { - templateId = req.body.templateId; - } else if ('id' in req.body.template) { - templateId = req.body.template.id; + const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN); + + if (decoded && ("id" in decoded) && decoded.id !== null && ("email" in decoded)) { + const vaEmail = decoded.email; + + const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }) + if (!vaUser) { + return res.status(401).json({ success: false, message: "Not Authorized" }); + } + if (!("accessToken" in vaUser) || !vaUser.accessToken) { + return res.status(401).json({ success: false, message: "Not Authorized" }); + } + + if (vaUser.accessToken === accessToken) { + req.body.vaId = vaUser._id + req.body.vaEmail = vaEmail + next(); + } else { + return res.status(401).json({ success: false, message: "Not Authorized" }); } - const tempRes = await templateService.fetch({ _id: templateId }); - const isPublish = tempRes.json_template !== null; - req.body.isPublish = isPublish; - orgId = tempRes.org_auth_id; } - - let authEntryExist = await authService.fetchByOrgId(orgId); - - if (!authEntryExist) { - logger.error("AuthValidator - Entry not exist templateId:", templateId); - return res.status(403).send({"message": "Entry not exist"}); - } - - let { instance_url, refresh_token, access_token, is_sandbox } = authEntryExist; - - access_token = decryptCryptr(access_token) - refresh_token = decryptCryptr(refresh_token) - - let isValid = await httpReqValidateAuth(orgId, access_token, instance_url, refresh_token) - - if (!isValid) { - logger.info("AuthValidator - Access_Token expired - refreshing ..."); - const refTokenRes = await httpReqRefToken(refresh_token, is_sandbox); - access_token = refTokenRes.access_token; - await authService.update({ org_id: orgId }, { access_token: encryptCryptr(access_token) }); - } - - isValid = await httpReqValidateAuth(orgId, access_token, instance_url, refresh_token) - - if (!isValid) { - logger.error("AuthValidator - Not Refreshing RefreshingToken Expired return 401. access_token"); - return res.send(401).status({"message": "Token expired (acc | ref)"}) - } - - req.body.access_token = access_token; - req.body.instance_url = instance_url; - - logger.info("AuthValidator - middleware Work Done (VALIDATED SUCCESSFULLY) ...") - next(); } catch (error) { logger.error("AuthValidator - Middleware (CATCH Block)", error.message); - return res.send(401).status({"message": error.message}) + return res.send(401).status({ "message": error.message }) } } diff --git a/src/models/vaModel.js b/src/models/vaModel.js index 3d718de..1cab614 100644 --- a/src/models/vaModel.js +++ b/src/models/vaModel.js @@ -62,6 +62,7 @@ const projectSchema = new Schema({ }, totalLoggedHours: { type: Number, + default: 0 }, vaAssigned: [ { @@ -113,6 +114,14 @@ const loggedSchema = new Schema({ vaId: { type: Types.ObjectId, ref: "VA" + }, + vaEmail: { + type: String, + required: true + }, + projectId: { + type: Types.ObjectId, + ref: "Project" } }) @@ -127,6 +136,12 @@ const vaSchema = new Schema( ref: "Client", }, ], + projectIds: [ + { + type: Types.ObjectId, + ref: "Project", + }, + ], schedule: { type: String, }, @@ -135,14 +150,47 @@ const vaSchema = new Schema( enum: ["PARTTIME", "FULLTIME", "INACTIVE"], default: "FULLTIME", }, + accessToken: { + type: String, + default: null + }, + refreshToken: { + type: String, + default: null + } }, { timestamps: true } ); +// Need to add working hours and time zone tooooo in which VA is going to work, need to calculate total work hours of the day - Schedules +// Need to add this Schedule feature on SF there we can change the schedulte of VA - Add in form tooo +// Need to ask to client if we want to provide schedule changing functionality on Docker toooooo + + +// Add IN OUT + + +// const vaTimeSheetSchema = new Schema({ +// vaId: { +// type: Types.ObjectId, +// ref: "VA", +// }, +// projectId: { +// type: Types.ObjectId, +// ref: "Project", +// }, +// logged: { +// enum: ["IN", "OUT"], +// default: "IN", +// } +// }, +// { timestamps: true } +// ) + /* ---------------------- Model Exports ---------------------- */ const VAModel = model("VA", vaSchema); const ClientModel = model("Client", clientSchema); const ProjectModel = model("Project", projectSchema); -const LoggedModel = model("Logged", loggedSchema); +const LoggedModel = model("TrackLog", loggedSchema); export { VAModel, ClientModel, ProjectModel, LoggedModel }; diff --git a/src/routes/v1/va.routes.js b/src/routes/v1/va.routes.js index be2c8b0..ea9d339 100644 --- a/src/routes/v1/va.routes.js +++ b/src/routes/v1/va.routes.js @@ -2,11 +2,14 @@ import express from "express"; const vaRoutes = express.Router(); import { va_callback, va_username_callback, va_clients_callback, va_clients_projects_callback, va_log_callback, va_project_log_callback, va_filter_search, va_projects_callback, va_register, va_login_callback, va_refresh_token_callback } from "../../controllers/va.controller.js"; +import { authValidator } from "../../middlewares/auth.middleware.js"; vaRoutes.get("/", va_callback); // VA Callback for getting active VA vaRoutes.post("/login", va_login_callback); // VA Callback for login VA +vaRoutes.get("/logout", authValidator, va_logout_callback); // VA Callback for login VA + vaRoutes.post("/refreshToken", va_refresh_token_callback); // VA Callback for refresh token vaRoutes.post("/register", va_register); // Register a new VA @@ -19,9 +22,10 @@ vaRoutes.get("/clients/:clientId/projects/:projectId", va_clients_projects_callb vaRoutes.get("/project/:projectId", va_projects_callback); // Projects Callback for getting project details -vaRoutes.get("/:id/log/:from/:to/:screentShot", va_log_callback); // VA Log Callback +// FROM || TO - +vaRoutes.get("/log/:projectId/:from?/:to?", authValidator, va_log_callback); // VA Log Callback -vaRoutes.get("/:id/log/project/:projectID", va_project_log_callback); // VA Logged Hours in specific Project +vaRoutes.post("/log/project", authValidator, va_project_log_callback); // VA Logged Hours in specific Project // VA filter search: supports query params `name`, `email`, `from`, `to` // Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 diff --git a/views/va_register.ejs b/views/va_register.ejs index bc646a1..6228575 100644 --- a/views/va_register.ejs +++ b/views/va_register.ejs @@ -4,132 +4,202 @@
-