code base

This commit is contained in:
ANUJ7MADKE
2025-07-13 22:49:55 +05:30
parent d4f21c9a99
commit cd43f0e98e
96 changed files with 17779 additions and 0 deletions

2
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/node_modules
.env

1518
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
backend/package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"dependencies": {
"@prisma/client": "^5.20.0",
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.16",
"prisma": "^5.20.0"
},
"name": "backend",
"version": "1.0.0",
"main": "src/server.js",
"devDependencies": {
"nodemon": "^3.1.4"
},
"scripts": {
"test2": "nodemon --inspect src/server.js",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon src/server.js",
"start": "node src/server.js"
},
"type": "module",
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}

View File

@@ -0,0 +1,101 @@
// Generator to create Prisma Client
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "darwin-arm64", "linux-musl-arm64-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
enum Institute {
KJSIDS
SKSC
KJSCE
SIRC
KJSIM
SSA
KJSCEd
DLIS
MSSMPA
}
enum ApplicationStatus {
REJECTED
ACCEPTED
PENDING
}
enum Designation {
HOD
HOI
VC
ACCOUNTS
FACULTY
STUDENT
}
model User {
profileId String @id @default(uuid())
userName String
email String @unique @db.Text
password String
institute Institute?
department String?
designation Designation
appliedApplications Application[] @relation("AppliedApplications")
toValidateApplications Application[] @relation("ToValidateApplications")
@@index([email])
}
model Application {
applicationId String @id @default(uuid())
applicantId String
applicant User @relation("AppliedApplications", fields: [applicantId], references: [profileId])
institute Institute
department String
applicantName String
applicationType String
formData Json
formName String
resubmission Boolean @default(false)
facultyValidation ApplicationStatus?
hodValidation ApplicationStatus?
hoiValidation ApplicationStatus?
vcValidation ApplicationStatus?
accountsValidation ApplicationStatus?
rejectionFeedback String?
totalExpense Float @default(0)
proofOfTravel Bytes?
proofOfAccommodation Bytes?
proofOfAttendance Bytes?
expenseProof0 Bytes?
expenseProof1 Bytes?
expenseProof2 Bytes?
expenseProof3 Bytes?
expenseProof4 Bytes?
expenseProof5 Bytes?
expenseProof6 Bytes?
expenseProof7 Bytes?
expenseProof8 Bytes?
expenseProof9 Bytes?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
validators User[] @relation("ToValidateApplications")
@@index([applicantId])
@@index([createdAt])
}

123
backend/prisma/seed.js Normal file
View File

@@ -0,0 +1,123 @@
import prisma from "../src/config/prismaConfig.js";
async function main() {
// Common password for all users
const commonPassword = "securePassword123";
// Applicant and Validator data
const institutes = [
"KJSIDS",
"SKSC",
"KJSCE",
"SIRC",
"KJSIM",
"SSA",
"KJSCEd",
"DLIS",
"MSSMPA",
];
const departments = [
"Mechanical",
"Electronics",
"CBE",
"Electronics & Telecommunication",
"Computer",
"Information Technology",
"Science & Humanities",
"Admin",
"Library",
];
// Create VC (single, no department or institute)
console.log("Seeding VC...");
await prisma.user.create({
data: {
userName: "Validator_VC",
email: "vc@example.com",
password: commonPassword,
designation: "VC",
},
});
for (const institute of institutes) {
// Create HOI for each institute
console.log(`Seeding HOI for ${institute}...`);
const hoiEmail = `hoi.${institute.toLowerCase()}@example.com`;
await prisma.user.create({
data: {
userName: `HOI_${institute}`,
email: hoiEmail,
password: commonPassword,
institute,
designation: "HOI",
},
});
// Create Accounts for each institute
console.log(`Seeding Accounts for ${institute}...`);
await prisma.user.create({
data: {
userName: `Validator_Accounts_${institute}`,
email: `accounts.${institute.toLowerCase()}@example.com`,
password: commonPassword,
institute,
designation: "ACCOUNTS",
},
});
for (const department of departments) {
// Create HOD for each department of each institute
console.log(`Seeding HOD for ${department} in ${institute}...`);
const hodEmail = `hod.${department.toLowerCase()}.${institute.toLowerCase()}@example.com`;
await prisma.user.create({
data: {
userName: `HOD_${department}_${institute}`,
email: hodEmail,
password: commonPassword,
institute,
department,
designation: "HOD",
},
});
// Create Faculty for each department of each institute
console.log(`Seeding Faculty for ${department} in ${institute}...`);
const facultyEmail = `faculty.${department.toLowerCase()}.${institute.toLowerCase()}@example.com`;
await prisma.user.create({
data: {
userName: `Faculty_${department}_${institute}`,
email: facultyEmail,
password: commonPassword,
institute,
department,
designation: "FACULTY",
},
});
// Create Student for each department of each institute
console.log(`Seeding Student for ${department} in ${institute}...`);
const studentEmail = `student.${department.toLowerCase()}.${institute.toLowerCase()}@example.com`;
await prisma.user.create({
data: {
userName: `Student_${department}_${institute}`,
email: studentEmail,
password: commonPassword,
institute,
department,
designation: "STUDENT",
},
});
}
console.log("Seeding completed!");
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
}).finally(async () => {
await prisma.$disconnect();
});

29
backend/src/app.js Normal file
View File

@@ -0,0 +1,29 @@
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import router from './routes/auth.js';
import applicantRoute from './routes/applicant.js';
import validatorRoute from './routes/validator.js';
import generalRoute from './routes/general.js';
import { verifyApplicantToken, verifyToken, verifyValidatorToken } from './middleware/verifyJwt.js';
const app = express();
// Middleware setup
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: true,
credentials: true
}));
// Route-specific middleware and routes
app.use('/applicant', verifyApplicantToken, applicantRoute);
app.use('/validator', verifyValidatorToken, validatorRoute);
app.use('/general', verifyToken, generalRoute);
// Authentication routes
app.use(router);
export default app;

View File

@@ -0,0 +1,16 @@
const applicantDesignations = [
'STUDENT',
'FACULTY',
'HOD',
'HOI',
];
const validatorDesignations = [
'FACULTY',
'HOD',
'HOI',
'VC',
'ACCOUNTS',
];
export {applicantDesignations, validatorDesignations}

View File

@@ -0,0 +1,5 @@
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default prisma;

View File

@@ -0,0 +1,434 @@
import { application } from "express";
import prisma from "../config/prismaConfig.js";
import sendMail from "../services/sendMail.js";
const createApplication = async (req, res) => {
const {
id: applicantId,
email,
designation: applicantDesignation,
department,
institute,
role,
} = req.user;
const formData = req.body;
try {
if (role !== "applicant") {
return res
.status(403)
.send({ message: "Forbidden, Sign In as Applicant" });
}
const applicant = await prisma.user.findUnique({
where: { profileId: applicantId },
});
if (!applicant) {
return res.status(404).send({ message: "User not Found" });
}
// check for the formName
// if form name is "Travel Intimation Form", then do nothing
// if form name is "Post Travel Form" then get the "intimationApplicationID" from the form and check if an application with that id exists if not then return an error
// then check if that form dosent have any validation pending or rejected
// if it has then return an error
// if not then create the application
const formName = formData.formName;
if (!formName){
return res.status(400).send({ message: "Form Name is required" });
}
if (formName === "Post Travel Form") {
const intimationApplicationID = formData.intimationApplicationID;
if (intimationApplicationID === null) {
return res.status(400).send({ message: "Intimation Application ID is required" });
}
const intimationApplication = await prisma.application.findUnique({
where: {
applicationId: intimationApplicationID,
applicantId: applicantId,
},
});
if (!intimationApplication) {
return res.status(404).send({ message: "Intimation Application not found" });
}
if ( intimationApplication["formName"] !== "Travel Intimation Form") {
return res.status(400).send({ message: "Intimation Application ID is not of a Travel Intimation Form" });
}
const validationFields = [
"facultyValidation",
"hodValidation",
"hoiValidation",
"vcValidation",
"accountsValidation",
];
const hasRejectedValidations = validationFields.some(
(field) => intimationApplication[field] === "REJECTED"
);
if (hasRejectedValidations) {
return res
.status(400)
.send({ message: "Intimation Application has rejected validations" });
}
const hasPendingValidations = validationFields.some(
(field) => intimationApplication[field] === "PENDING"
);
if (hasPendingValidations) {
return res
.status(400)
.send({ message: "Intimation Application has pending validations" });
}
}
const applicantName = applicant.userName;
let primarySupervisor,
anotherSupervisor,
hod,
hoi,
vc = null;
switch (applicant.designation) {
case "STUDENT":
primarySupervisor = await prisma.user.findUnique({
where: {
email: formData.primarySupervisorEmail,
department,
designation: "FACULTY",
institute,
},
});
if (!primarySupervisor) {
return res.status(404).send({ message: "Faculty not found (Incorrect Primary Supervisor Email)" });
}
anotherSupervisor = await prisma.user.findUnique({
where: {
email: formData.anotherSupervisorEmail,
department: formData.anotherSupervisorDepartment,
designation: "FACULTY",
institute,
},
});
if (!anotherSupervisor && formData.anotherSupervisorEmail) {
return res.status(404).send({ message: "Another Supervisor not found" });
}
break;
case "FACULTY":
hod = await prisma.user.findFirst({
where: { department, designation: "HOD", institute },
});
if (!hod) {
return res.status(404).send({ message: "HOD not found" });
}
break;
case "HOD":
hoi = await prisma.user.findFirst({
where: { designation: "HOI", institute },
});
if (!hoi) {
return res.status(404).send({ message: "HOI not found" });
}
break;
case "HOI":
vc = await prisma.user.findFirst({
where: { designation: "VC" },
});
if (!vc) {
return res.status(404).send({ message: "VC not found" });
}
break;
default:
break;
}
// Security check: Make sure the proper relationships exist between the applicant and application data
if (applicant.designation === "STUDENT") {
// Validate that primary supervisor email is provided for student applications
if (!formData.primarySupervisorEmail) {
return res.status(400).send({ message: "Primary supervisor email is required for student applications" });
}
}
// Compile the validators list with available supervisors, FDC coordinator, HOD, and HOI
const validators = [
primarySupervisor && { profileId: primarySupervisor?.profileId },
anotherSupervisor && { profileId: anotherSupervisor?.profileId },
hod && { profileId: hod?.profileId },
hoi && { profileId: hoi?.profileId },
vc && { profileId: vc?.profileId },
].filter(Boolean);
const {
proofOfTravel,
proofOfAccommodation,
proofOfAttendance,
...otherFiles
} = req.files;
// Prepare file buffers for fixed fields
const proofOfTravelBuffer = proofOfTravel?.[0]?.buffer || null;
const proofOfAccommodationBuffer =
proofOfAccommodation?.[0]?.buffer || null;
const proofOfAttendanceBuffer = proofOfAttendance?.[0]?.buffer || null;
// Prepare an object to hold the expense proof buffers dynamically
const expenseProofs = {};
for (let i = 0; i < 10; i++) {
const expenseProofField = `expenses[${i}].expenseProof`;
if (otherFiles[expenseProofField]) {
expenseProofs[`expenseProof${i}`] =
otherFiles[expenseProofField][0].buffer;
}
}
const expenses = JSON.parse(formData.expenses);
const totalExpense = parseFloat(
expenses.reduce(
(total, { expenseAmount }) => total + +expenseAmount,
0
).toFixed(2)
);
formData["totalExpense"] = totalExpense;
// Construct the application data object
const applicationData = {
applicantName,
department,
institute,
totalExpense,
formData,
proofOfTravel: proofOfTravelBuffer,
proofOfAccommodation: proofOfAccommodationBuffer,
proofOfAttendance: proofOfAttendanceBuffer,
...expenseProofs, // Add dynamically generated expense proof fields
facultyValidation: ["STUDENT"].includes(applicant.designation)
? "PENDING"
: undefined,
hodValidation: ["FACULTY"].includes(applicant.designation)
? "PENDING"
: undefined,
hoiValidation: ["HOD"].includes(applicant.designation)
? "PENDING"
: undefined,
vcValidation: ["HOI"].includes(applicant.designation)
? "PENDING"
: undefined,
};
// Create new application entry with linked applicant and validators
const newApplication = await prisma.application.create({
data: {
...applicationData,
applicationType: applicant.designation === "STUDENT" ? "STUDENT" : "FACULTY",
applicant: {
connect: { profileId: applicantId },
},
validators: {
connect: validators,
},
formName: formData.formName,
},
});
// Store this original state to compare on future modifications
// We don't need to actually create a new record, the application itself
// will serve this purpose
// sendMail({
// emailId: hod.email,
// link: `http://localhost:5173/validator/dashboard/pending/${newApplication.applicationId}`,
// type: "validator",
// status: null,
// designation: null,
// });
res.status(201).send({
message: "Application created successfully",
data: newApplication.applicantName,
});
} catch (error) {
console.error("Error creating application:", error);
res.status(500).send({
message: "Error creating application",
error: error.message,
});
}
};
const updateApplication = async (req, res) => {
const {
id: applicantId,
email,
designation: applicantDesignation,
department,
institute,
role,
} = req.user;
const formData = req.body;
const applicationId = formData.applicationId;
delete formData.applicationId;
try {
if (role !== "applicant") {
return res
.status(403)
.send({ message: "Forbidden, Sign In as Applicant" });
}
const applicant = await prisma.user.findUnique({
where: { profileId: applicantId },
});
if (!applicant) {
return res.status(404).send({ message: "User not Found" });
}
// Fetch the original application to compare fields
const originalApplication = await prisma.application.findUnique({
where: { applicationId },
});
if (!originalApplication) {
return res.status(404).send({ message: "Application not found" });
}
const originalFormData = originalApplication.formData;
// Verify that disabled fields haven't been changed
// Only expenses can be edited, everything else should remain the same regardless of resubmission status
// Create a clone of the original form data for comparison
const safeOriginalData = JSON.parse(JSON.stringify(originalFormData));
// Create a safe copy of submitted form data for validation
const safeSubmittedData = { ...formData };
// Exclude expenses-related fields from comparison as they're allowed to change
delete safeOriginalData.expenses;
delete safeSubmittedData.expenses;
delete safeSubmittedData.resubmission;
delete safeOriginalData.totalExpense;
delete safeSubmittedData.totalExpense;
delete safeOriginalData?.proofOfTravel;
delete safeOriginalData?.proofOfAccommodation;
delete safeSubmittedData?.proofOfAttendance;
// We need to check if any non-expenses fields have been modified
for (const key in safeSubmittedData) {
// Don't check expense fields pattern (expenses[0].expenseProof, etc.)
if (key.startsWith('expenses[') || key === 'proofOfTravel' || key === 'proofOfAccommodation' || key === 'proofOfAttendance') {
continue;
}
// Check if the field has been modified
if (safeSubmittedData[key] !== safeOriginalData[key]) {
console.log(`Tampering detected: Field '${key}' was modified`);
console.log(`Original: ${safeOriginalData[key]}`);
console.log(`Submitted: ${safeSubmittedData[key]}`);
return res.status(403).send({
message: `Forbidden: Field '${key}' cannot be modified. Only expense information can be changed. Tampering detected.`,
});
}
}
const {
proofOfTravel,
proofOfAccommodation,
proofOfAttendance,
...otherFiles
} = req.files;
// Prepare file buffers for fixed fields
const proofOfTravelBuffer = proofOfTravel?.[0]?.buffer || null;
const proofOfAccommodationBuffer =
proofOfAccommodation?.[0]?.buffer || null;
const proofOfAttendanceBuffer = proofOfAttendance?.[0]?.buffer || null;
// Prepare an object to hold the expense proof buffers dynamically
const expenseProofs = {};
for (let i = 0; i < 10; i++) {
const expenseProofField = `expenses[${i}].expenseProof`;
if (otherFiles[expenseProofField]) {
expenseProofs[`expenseProof${i}`] =
otherFiles[expenseProofField][0].buffer;
}
}
const expenses = JSON.parse(formData.expenses);
const totalExpense = parseFloat(
expenses.reduce(
(total, { expenseAmount }) => total + +expenseAmount,
0
).toFixed(2)
);
formData["totalExpense"] = totalExpense;
const validationFields = [
"facultyValidation",
"hodValidation",
"hoiValidation",
"vcValidation",
"accountsValidation",
];
console.log(expenseProofs)
const updatedData = {
totalExpense,
formData,
proofOfTravel: proofOfTravelBuffer,
proofOfAccommodation: proofOfAccommodationBuffer,
proofOfAttendance: proofOfAttendanceBuffer,
resubmission: false,
...expenseProofs,
};
for (const field of validationFields) {
if (originalApplication[field] === "REJECTED") {
updatedData[field] = "PENDING";
}
}
const updatedApplication = await prisma.application.update({
where: { applicationId },
data: updatedData,
});
res.status(200).send({
message: "Application updated successfully",
data: updatedApplication.applicantName,
});
} catch (error) {
res.status(500).send({
message: "Error updating application",
error: error.message,
});
}
}
export { createApplication, updateApplication };

View File

@@ -0,0 +1,130 @@
import prisma from "../config/prismaConfig.js";
import generateToken from "../services/generateToken.js";
const applicantLogin = async (req, res) => {
try {
const { email, password } = req.body;
// Check if the applicant profile exists
const validProfile = await prisma.user.findUnique({
where: {
email
},
});
if (!validProfile) {
return res.status(404).json({
message: "Applicant User Doesn't exist",
data: null,
});
}
// Check if the password is correct
if (validProfile.password !== password) {
return res.status(404).json({
message: "Wrong Password",
data: null,
});
}
// Create token object
const tokenObject = {
id: validProfile.profileId,
designation: validProfile.designation,
department: validProfile.department,
institute: validProfile.institute,
role: "applicant",
};
// Generate the token
const token = generateToken(tokenObject);
// Set the token as a cookie
return res
.cookie("access_token", token, { sameSite: 'None', secure: true, httpOnly: true })
.status(200)
.json({
message: "Login Successful",
data: { username: validProfile.userName, token },
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
data: error.message,
});
}
};
const validatorLogin = async (req, res) => {
try {
const { email, password } = req.body;
// Check if the validator profile exists
let validProfile = await prisma.user.findUnique({
where: {
email
},
});
if (!validProfile) {
return res.status(404).json({
message: "Validator User Doesn't exist",
data: null,
});
}
// Check if the password is correct
if (validProfile.password !== password) {
return res.status(404).json({
message: "Wrong Password",
data: null,
});
}
// Create token object
const tokenObject = {
id: validProfile.profileId,
designation: validProfile.designation,
department: validProfile.department,
institute: validProfile.institute,
role: "validator",
};
// Generate the token
const token = generateToken(tokenObject);
// Set the token as a cookie
return res
.cookie("access_token", token, { sameSite: 'None', secure: true, httpOnly: true })
.status(200)
.json({
message: "Login Successful",
data: { username: validProfile.userName, token },
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
data: error.message,
});
}
};
const logout = async (req, res) => {
try {
// Clear the access token cookie
res.clearCookie("access_token", { httpOnly: true });
// Respond with success message
return res.status(200).json({
message: "Logout Successful",
data: null,
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
data: error.message,
});
}
};
export { applicantLogin, validatorLogin, logout };

View File

@@ -0,0 +1,115 @@
const u = {
expenses:
'[{"expenseCategory":"TRAVEL","expenseName":"Rustic Granite Ball","expenseAmount":3696,"expenseProof":{}}]',
cadreSize: "",
eventName: "Rustic Metal Chair",
eventVenue: "",
applicantAge: "",
eventEndDate: "",
eventWebsite: "",
modeOfTravel: "",
totalExpense: 3696,
typeOfTravel: "",
applicantIDNo: "",
proofOfTravel: "",
travelEndDate: "",
applicantEmail: "",
durationOfStay: "",
eventStartDate: "",
applicationType: "Individual",
cadreMember1Age: "",
cadreMember2Age: "",
cadreMember3Age: "",
cadreMember4Age: "",
cadreMember5Age: "",
cadreMember6Age: "",
cadreMember7Age: "",
cadreMember8Age: "",
cadreMember9Age: "",
purposeOfTravel: "",
travelStartDate: "",
applicantAddress: "3491 Margarette Plains",
applicantContact: "249-639-5239",
cadreMember10Age: "",
cadreMember1IDNo: "",
cadreMember2IDNo: "",
cadreMember3IDNo: "",
cadreMember4IDNo: "",
cadreMember5IDNo: "",
cadreMember6IDNo: "",
cadreMember7IDNo: "",
cadreMember8IDNo: "",
cadreMember9IDNo: "",
applicantFullName: "Rick Bahringer",
cadreMember10IDNo: "",
cadreMember1Email: "",
cadreMember2Email: "",
cadreMember3Email: "",
cadreMember4Email: "",
cadreMember5Email: "",
cadreMember6Email: "",
cadreMember7Email: "",
cadreMember8Email: "",
cadreMember9Email: "",
modeOfTravelOther: "",
proofOfAttendance: "",
accommodationOpted: "false",
applicantInstitute: "KJSCE",
cadreMember10Email: "",
applicantDepartment: "Computer",
cadreMember1Address: "",
cadreMember1Contact: "",
cadreMember2Address: "",
cadreMember2Contact: "",
cadreMember3Address: "",
cadreMember3Contact: "",
cadreMember4Address: "",
cadreMember4Contact: "",
cadreMember5Address: "",
cadreMember5Contact: "",
cadreMember6Address: "",
cadreMember6Contact: "",
cadreMember7Address: "",
cadreMember7Contact: "",
cadreMember8Address: "",
cadreMember8Contact: "",
cadreMember9Address: "",
cadreMember9Contact: "",
typeOfAccommodation: "",
accommodationAddress: "",
anyOtherRequirements: "",
cadreMember10Address: "",
cadreMember10Contact: "",
cadreMember1FullName: "",
cadreMember2FullName: "",
cadreMember3FullName: "",
cadreMember4FullName: "",
cadreMember5FullName: "",
cadreMember6FullName: "",
cadreMember7FullName: "",
cadreMember8FullName: "",
cadreMember9FullName: "",
proofOfAccommodation: "",
purposeOfTravelOther: "",
cadreMember10FullName: "",
cadreMember1Institute: "",
cadreMember2Institute: "",
cadreMember3Institute: "",
cadreMember4Institute: "",
cadreMember5Institute: "",
cadreMember6Institute: "",
cadreMember7Institute: "",
cadreMember8Institute: "",
cadreMember9Institute: "",
cadreMember10Institute: "",
cadreMember1Department: "",
cadreMember2Department: "",
cadreMember3Department: "",
cadreMember4Department: "",
cadreMember5Department: "",
cadreMember6Department: "",
cadreMember7Department: "",
cadreMember8Department: "",
cadreMember9Department: "",
cadreMember10Department: "",
};

View File

@@ -0,0 +1,439 @@
import { application } from "express";
import prisma from "../config/prismaConfig.js";
import {
applicantDesignations,
validatorDesignations,
} from "../config/designations.js";
const dataRoot = async (req, res) => {
try {
const user = req.user; // Contains all user info (id, designation, department, etc.)
const { id: profileId, email, designation, department, role } = user;
if (applicantDesignations.includes(designation) && role === "applicant") {
const applicant = await prisma.user.findUnique({
where: { profileId },
});
if (!applicant) {
return res
.status(404)
.json({ message: "Applicant not found", data: null });
}
delete applicant.password;
return res.status(200).json({
message: "Applicant Authorized",
user: applicant,
role: "Applicant",
});
} else if (
validatorDesignations.includes(designation) &&
role === "validator"
) {
const validator = await prisma.user.findUnique({
where: { profileId },
});
if (!validator) {
return res
.status(404)
.json({ message: "Validator not found", data: null });
}
delete validator.password;
return res.status(200).json({
message: "Validator Authorized",
user: validator,
role: "Validator",
});
} else {
return res
.status(403)
.json({ message: "Unauthorized access", data: null });
}
} catch (error) {
// Handle any errors that occur during the process
console.error(error);
return res
.status(500)
.json({ message: "Internal Server Error", data: null });
}
};
const getApplicationsByStatus = async (req, res) => {
try {
const user = req.user;
const userId = user.id;
const take = parseInt(req.query.take) || 5;
const skip = parseInt(req.query.skip) || 0;
const status = req.params.status.toUpperCase(); // Expected: "PENDING", "ACCEPTED", or "REJECTED"
const sortBy = req.query?.sortBy;
const sortValue = req.query?.sortValue;
const validStatuses = ["PENDING", "ACCEPTED", "REJECTED"];
if (!validStatuses.includes(status)) {
return res.status(400).send("Invalid status");
}
let applications, totalApplications;
// Filter conditions for Student and Faculty
if (
applicantDesignations.includes(user.designation) &&
user.role === "applicant"
) {
const baseWhere = {
applicantId: userId,
...(status === "PENDING" && {
OR: [
{ facultyValidation: "PENDING" },
{ hodValidation: "PENDING" },
{ hoiValidation: "PENDING" },
{ vcValidation: "PENDING" },
{ accountsValidation: "PENDING" },
],
}),
...(status === "ACCEPTED" && {
AND: [
{ OR: [{ facultyValidation: "ACCEPTED" }, { facultyValidation: null }] },
{ OR: [{ hodValidation: "ACCEPTED" }, { hodValidation: null }] },
{ OR: [{ hoiValidation: "ACCEPTED" }, { hoiValidation: null }] },
{ OR: [{ vcValidation: "ACCEPTED" }, { vcValidation: null }] },
{
OR: [
{ accountsValidation: "ACCEPTED" },
{ accountsValidation: null },
],
},
],
}),
...(status === "REJECTED" && {
OR: [
{ facultyValidation: "REJECTED" },
{ hodValidation: "REJECTED" },
{ hoiValidation: "REJECTED" },
{ vcValidation: "REJECTED" },
{ accountsValidation: "REJECTED" },
],
}),
};
// Apply case-insensitive filter for search functionality
if (sortBy && sortValue) {
baseWhere[sortBy] = {
contains: sortValue,
mode: "insensitive",
};
}
// Count and fetch applications
totalApplications = await prisma.application.count({ where: baseWhere });
applications = await prisma.application.findMany({
where: baseWhere,
select: {
applicationId: true,
applicantName: true,
formData: true,
createdAt: true,
},
take,
skip,
orderBy: { createdAt: "desc" },
});
// Filter conditions for Validators (Supervisor, HOD, HOI, FDCcoordinator)
} else if (
validatorDesignations.includes(user.designation) &&
user.role === "validator"
) {
const validationField = `${user.designation.toLowerCase()}Validation`;
const baseWhere = {
validators: { some: { profileId: userId } },
[validationField]: status,
};
if (sortBy && sortValue) {
baseWhere[sortBy] = {
contains: sortValue,
mode: "insensitive",
};
}
totalApplications = await prisma.application.count({
where: baseWhere,
});
applications = await prisma.application.findMany({
where: baseWhere,
select: {
applicationId: true,
applicantName: true,
formData: true,
createdAt: true,
},
take,
skip,
orderBy: { createdAt: "desc" },
});
} else {
// Unauthorized access for other user roles
return res.status(403).send("Unauthorized");
}
// Format response with selected fields
const responseApplications = applications.map((application) => ({
applicationId: application.applicationId,
applicantName: application.applicantName,
formData: {
eventName: application.formData.eventName,
applicantDepartment: application.formData.applicantDepartment,
},
createdAt: application.createdAt,
}));
return res.status(200).json({
message: `${status} Applications Fetched Successfully`,
totalApplications,
applications: responseApplications,
});
} catch (error) {
console.error(error);
res.status(500).send(error.message);
}
};
const getApplicationData = async (req, res) => {
try {
const applicationId = req.params.applicationId;
const user = req.user;
// Fetch application data excluding proof fields
const applicationFull = await prisma.application.findUnique({
where: {
applicationId: applicationId,
},
select: {
applicationId: true,
applicantId: true,
applicantName: true,
resubmission: true,
formData: true,
facultyValidation: true,
hodValidation: true,
hoiValidation: true,
vcValidation: true,
accountsValidation: true,
rejectionFeedback: true,
createdAt: true,
applicant: {
select: {
designation: true,
},
},
validators: {
select: {
profileId: true,
},
},
},
});
if (
applicationFull?.applicantId !== user.id &&
!applicationFull.validators.some(
(validator) => validator.profileId === user.id
)
) {
return res.status(403).json({
message: "Unauthorized",
data: null,
});
}
if (!applicationFull) {
return res.status(404).json({
message: "Application not found",
data: null,
});
}
let currentStatus;
// Check if the user is an applicant or a validator
if (
applicantDesignations.includes(user.designation) &&
user.role === "applicant"
) {
if (
applicationFull.facultyValidation === "PENDING" ||
applicationFull.hodValidation === "PENDING" ||
applicationFull.hoiValidation === "PENDING" ||
applicationFull.vcValidation === "PENDING" ||
applicationFull.accountsValidation === "PENDING"
) {
currentStatus = "PENDING";
} else if (
applicationFull.facultyValidation === "REJECTED" ||
applicationFull.supervisorValidation === "REJECTED" ||
applicationFull.hodValidation === "REJECTED" ||
applicationFull.hoiValidation === "REJECTED" ||
applicationFull.fdccoordinatorValidation === "REJECTED"
) {
currentStatus = "REJECTED";
} else {
currentStatus = "ACCEPTED";
}
} else if (
validatorDesignations.includes(user.designation) &&
user.role === "validator"
) {
const validationField = `${user.designation.toLowerCase()}Validation`;
if (applicationFull[validationField] === "ACCEPTED") {
currentStatus = "ACCEPTED";
} else if (applicationFull[validationField] === "REJECTED") {
currentStatus = "REJECTED";
} else {
currentStatus = "PENDING";
}
} else {
return res.status(403).json({
message: "Unauthorized",
data: null,
});
}
// Respond with the full application data and current status
return res.status(200).json({
message: "Application data retrieved successfully",
data: { ...applicationFull, currentStatus },
});
} catch (error) {
console.error("Error retrieving application data:", error);
return res.status(500).json({
message: "An error occurred while retrieving the application data",
data: null,
});
}
};
const getFile = async (req, res) => {
try {
const { applicationId, fileName } = req.params;
const user = req.user;
const userId = user.id;
const validFileNames = [
"proofOfTravel",
"proofOfAccommodation",
"proofOfAttendance",
"expenseProof0",
"expenseProof1",
"expenseProof2",
"expenseProof3",
"expenseProof4",
"expenseProof5",
"expenseProof6",
"expenseProof7",
"expenseProof8",
"expenseProof9",
];
if (!validFileNames.includes(fileName)) {
return res.status(400).json({ error: "Invalid File request" });
}
const fileSelection = {
proofOfTravel: false,
proofOfAccommodation: false,
proofOfAttendance: false,
expenseProof0: false,
expenseProof1: false,
expenseProof2: false,
expenseProof3: false,
expenseProof4: false,
expenseProof5: false,
expenseProof6: false,
expenseProof7: false,
expenseProof8: false,
expenseProof9: false,
};
if (validFileNames.includes(fileName)) {
fileSelection[fileName] = true;
}
let myApplication, myFile;
if (
applicantDesignations.includes(user.designation) &&
user.role === "applicant"
) {
myApplication = await prisma.user.findUnique({
where: {
profileId: userId,
},
select: {
appliedApplications: {
where: {
applicationId,
},
select: fileSelection,
},
},
});
myFile = myApplication?.appliedApplications[0];
} else if (
validatorDesignations.includes(user.designation) &&
user.role === "validator"
) {
myApplication = await prisma.user.findUnique({
where: {
profileId: userId,
},
select: {
toValidateApplications: {
where: {
applicationId,
},
select: fileSelection,
},
},
});
myFile = myApplication?.toValidateApplications[0];
}
if (!myFile) {
return res.status(404).json({ error: "File not found" });
}
// Retrieve the file buffer dynamically based on the fileName
const fileBuffer = myFile[fileName];
// If file buffer doesn't exist
if (!fileBuffer) {
return res.status(404).json({ error: "File content not found" });
}
// Set response headers for PDF file download
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${fileName}.pdf"`
);
// Send the file buffer as a response
return res.send(fileBuffer);
} catch (error) {
console.error("Error retrieving application data:", error);
return res.status(500).json({
error: "An error occurred while retrieving the application data",
});
}
};
export { getApplicationData, getFile, dataRoot, getApplicationsByStatus };

View File

@@ -0,0 +1,437 @@
import { validatorDesignations } from "../config/designations.js";
import prisma from "../config/prismaConfig.js";
import sendMail from "../services/sendMail.js";
const applicationAction = async (req, res) => {
const { id: profileId, designation, department, institute, role } = req.user;
try {
const {
applicationId,
action,
rejectionFeedback,
toVC,
resubmission,
expenses,
} = req.body; // actions = 'accepted' or 'rejected'
if (role !== "validator") {
return res.status(403).send("Forbidden, Sign in as a validator");
}
const validator = await prisma.user.findFirst({
where: { profileId },
include: {
toValidateApplications: {
where: { applicationId },
include: {
validators: {
select: { profileId: true, designation: true },
},
},
},
},
});
if (!validator) {
return res.status(404).send("Validator doesn't exist");
}
const application = validator.toValidateApplications[0];
if (!application) {
return res.status(404).send("Application not available");
}
const applicant = await prisma.user.findFirst({
where: { profileId: application.applicantId },
});
const applicantDesignation = applicant.designation;
const applicantDepartment = applicant.department;
const applicantInstitute = applicant.institute;
const applicantEmail = applicant.email;
const validationStatus = action.toUpperCase();
let resubmissionStatus = JSON.parse(resubmission) || false;
if (validationStatus !== "ACCEPTED" && validationStatus !== "REJECTED") {
return res.status(400).send("Invalid status");
}
const validationData = {};
let hod,
hoi,
vc,
accounts = null;
switch (validator.designation) {
case "FACULTY":
if (application.facultyValidation != "PENDING") {
return res
.status(400)
.send("Already performed an action, can't change status again");
}
validationData.facultyValidation = validationStatus;
if (validationStatus === "ACCEPTED") {
validationData.hodValidation = "PENDING";
hod = await prisma.user.findFirst({
where: {
designation: "HOD",
department: applicantDepartment,
institute: applicantInstitute,
},
});
sendMail({
emailId: hod.email,
link: `http://localhost:5173/validator/dashboard/pending/${applicationId}`,
type: "validator",
status: null,
designation: null,
});
}
sendMail({
emailId: applicantEmail,
link: `http://localhost:5173/applicant/dashboard/${validationStatus}/${applicationId}`,
type: "applicant",
status: validationStatus,
designation: "FACULTY",
});
break;
case "HOD":
if (application.hodValidation != "PENDING") {
return res
.status(400)
.send("Already performed an action, can't change status again");
}
validationData.hodValidation = validationStatus;
if (validationStatus === "ACCEPTED") {
validationData.hoiValidation = "PENDING";
hoi = await prisma.user.findFirst({
where: { designation: "HOI", institute: applicantInstitute },
});
sendMail({
emailId: hoi.email,
link: `http://localhost:5173/validator/dashboard/pending/${applicationId}`,
type: "validator",
status: null,
designation: null,
});
}
sendMail({
emailId: applicantEmail,
link: `http://localhost:5173/applicant/dashboard/${validationStatus}/${applicationId}`,
type: "applicant",
status: validationStatus,
designation: "HOD",
});
break;
case "HOI":
if (application.hoiValidation != "PENDING") {
return res
.status(400)
.send("Already performed an action, can't change status again");
}
validationData.hoiValidation = validationStatus;
if (validationStatus === "ACCEPTED") {
if (JSON.parse(toVC)) {
if (applicantDesignation === "STUDENT") {
return {
status: 400,
message:
"Students Applications cannot be forwared for VC validation",
};
}
validationData.vcValidation = "PENDING";
vc = await prisma.user.findFirst({
where: {
designation: "VC",
},
});
sendMail({
emailId: vc.email,
link: `http://localhost:5173/validator/dashboard/pending/${applicationId}`,
type: "validator",
status: null,
designation: null,
});
} else {
validationData.accountsValidation = "PENDING";
accounts = await prisma.user.findFirst({
where: { designation: "ACCOUNTS", institute: applicantInstitute },
});
sendMail({
emailId: accounts.email,
link: `http://localhost:5173/validator/dashboard/pending/${applicationId}`,
type: "accounts",
status: null,
designation: null,
});
}
}
sendMail({
emailId: applicantEmail,
link: `http://localhost:5173/applicant/dashboard/${validationStatus}/${applicationId}`,
type: "applicant",
status: validationStatus,
designation: "HOI",
});
break;
case "VC":
if (application.vcValidation != "PENDING") {
return res
.status(400)
.send("Already performed an action, can't change status again");
}
validationData.vcValidation = validationStatus;
if (validationStatus === "ACCEPTED") {
validationData.accountsValidation = "PENDING";
accounts = await prisma.user.findFirst({
where: { designation: "ACCOUNTS", institute: applicantInstitute },
});
sendMail({
emailId: accounts.email,
link: `http://localhost:5173/validator/dashboard/pending/${applicationId}`,
type: "accounts",
status: null,
designation: null,
});
}
sendMail({
emailId: applicantEmail,
link: `http://localhost:5173/applicant/dashboard/${validationStatus}/${applicationId}`,
type: "applicant",
status: validationStatus,
designation: "VC",
});
break;
case "ACCOUNTS":
if (application.accountsValidation != "PENDING") {
return res
.status(400)
.send("Already performed an action, can't change status again");
}
validationData.accountsValidation = validationStatus;
sendMail({
emailId: applicantEmail,
link: `http://localhost:5173/applicant/dashboard/${validationStatus}/${applicationId}`,
type: "applicant",
status: validationStatus,
designation: "ACCOUNTS",
});
break;
default:
return res.status(400).send("Invalid validator designation");
}
const validators = [
hod && { profileId: hod?.profileId },
hoi && { profileId: hoi?.profileId },
vc && { profileId: vc?.profileId },
accounts && { profileId: accounts?.profileId },
].filter(Boolean);
if (rejectionFeedback) {
validationData.rejectionFeedback = rejectionFeedback;
}
if (validationStatus === "ACCEPTED") {
validationData.rejectionFeedback = null;
}
const newFormData = application.formData;
if (expenses) {
newFormData.expenses = expenses;
}
const response = await prisma.application.update({
where: {
applicationId: applicationId,
},
data: {
...validationData,
resubmission: resubmissionStatus,
validators: {
connect: validators,
},
formData: newFormData,
},
select: { applicationId: true },
});
res.status(200).send(response);
} catch (error) {
res.status(500).send(error.message);
}
};
const expenseAction = async (req, res) => {
const { id: profileId, designation, department, institute, role } = req.user;
try {
const { applicationId, expense, action } = req.body;
if (role !== "validator") {
return res.status(403).send("Forbidden, Sign in as a validator");
}
const validator = await prisma.user.findFirst({
where: { profileId },
include: {
toValidateApplications: {
where: { applicationId },
include: {
validators: {
select: { profileId: true, designation: true },
},
},
},
},
});
if (!validator) {
return res.status(404).send("Validator doesn't exist");
}
const application = validator.toValidateApplications[0];
if (!application) {
return res.status(404).send("Application not available");
}
const updatedFormData = {
...application.formData,
expenses: JSON.stringify(
JSON.parse(application.formData.expenses).map((singleExpense) =>
singleExpense.expenseId === expense.expenseId
? { ...singleExpense, proofStatus: action }
: singleExpense
)
),
};
const updatedApplication = await prisma.application.update({
where: {
applicationId,
},
data: {
formData: updatedFormData,
},
});
res.status(200).send(updatedApplication);
} catch (error) {
res.status(500).send(error.message);
}
};
const getApplicantNames = async (req, res) => {
const { id: profileId, designation, department, institute, role } = req.user;
try {
if (role !== "validator") {
return res.status(403).send("Forbidden");
}
const applicants = await prisma.application.findMany({
where: { validators: { some: { profileId } } },
select: {
applicantName: true,
},
distinct: ["applicantName"],
});
const ApplicantNames = applicants.map((application) => ({
key: application.applicantName.toLowerCase(),
value: application.applicantName,
}));
res.status(200).send(ApplicantNames);
} catch (error) {
console.error(error);
res.status(500).send(error.message);
}
};
const getReportData = async (req, res) => {
const { institute, department, year, applicationType } = req.query;
const {
id: profileId,
designation,
department: ogDepartment,
institute: ogInstitute,
role,
} = req.user;
try {
if (
(ogDepartment && department !== ogDepartment) ||
(ogInstitute && institute !== ogInstitute)
) {
return res.status(403).send("Forbidden");
}
const whereClause = {
institute,
department,
};
if (year) {
whereClause.createdAt = {
gte: new Date(`${year}-01-01`),
lt: new Date(`${year}-12-31`),
};
}
if (applicationType) {
whereClause.applicationType = applicationType;
}
const applications = await prisma.application.findMany({
where: whereClause,
});
const reportData = {
totalApplications: applications.length,
acceptedApplications: applications.filter(
(application) =>
(application.facultyValidation === "ACCEPTED" ||
application.facultyValidation === null) &&
(application.hodValidation === "ACCEPTED" ||
application.hodValidation === null) &&
(application.hoiValidation === "ACCEPTED" ||
application.hoiValidation === null) &&
(application.vcValidation === "ACCEPTED" ||
application.vcValidation === null) &&
(application.accountsValidation === "ACCEPTED" ||
application.accountsValidation === null)
),
rejectedApplications: applications.filter(
(application) =>
application.facultyValidation === "REJECTED" ||
application.hodValidation === "REJECTED" ||
application.hoiValidation === "REJECTED" ||
application.vcValidation === "REJECTED" ||
application.accountsValidation === "REJECTED"
),
pendingApplications: applications.filter(
(application) =>
application.facultyValidation === "PENDING" ||
application.hodValidation === "PENDING" ||
application.hoiValidation === "PENDING" ||
application.vcValidation === "PENDING" ||
application.accountsValidation === "PENDING"
),
};
res.status(200).send(reportData);
} catch (error) {
console.error(error);
res.status(500).send(error.message);
}
};
export { applicationAction, expenseAction, getApplicantNames, getReportData };

View File

@@ -0,0 +1,24 @@
import multer from 'multer';
const storage = multer.memoryStorage();
const upload = multer({ storage });
const uploadExpenses = (req, res, next) => {
const expenseProofFields = Array.from({ length: 10 }, (_, index) => ({
name: `expenses[${index}].expenseProof`,
maxCount: 1,
}));
const fields = [
{ name: 'proofOfTravel', maxCount: 1 },
{ name: 'proofOfAccommodation', maxCount: 1 },
{ name: 'proofOfAttendance', maxCount: 1 },
...expenseProofFields,
];
upload.fields(fields)(req, res, next);
};
export default uploadExpenses;

View File

@@ -0,0 +1,97 @@
import jwt from 'jsonwebtoken';
import { applicantDesignations, validatorDesignations } from '../config/designations.js';
const verifyApplicantToken = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.status(401).json({message: "No token found"});
}
jwt.verify(token, process.env.JWT_SECRET, (err,payload)=>{
if (err) {
return res.status(403).json({message:"Invalid Token"});
}
req.user = {
id : payload.id,
designation : payload.designation,
department : payload.department,
institute : payload.institute,
role : payload.role
};
if (req.user && applicantDesignations.includes(req.user.designation)) {
next();
} else {
return res.status(401).json({message : "Access denied. Not a applicant."});
}
})
};
const verifyValidatorToken = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.status(401).json({message: "No token found"});
}
jwt.verify(token, process.env.JWT_SECRET, (err,payload)=>{
if (err) {
return res.status(403).json({message:"Invalid Token"});
}
req.user = {
id : payload.id,
designation : payload.designation,
department : payload.department,
institute : payload.institute,
role : payload.role
}
if (req.user && validatorDesignations.includes(req.user.designation)) {
next();
} else {
return res.status(401).json({message:"Access denied. Not a validator."});
}
})
};
const verifyToken = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.status(401).json({message: "No token found"});
}
jwt.verify(token, process.env.JWT_SECRET, (err,payload)=>{
if (err) {
return res.status(403).json({message:"Invalid Token"});
}
req.user = {
id : payload.id,
designation : payload.designation,
department : payload.department,
institute : payload.institute,
role : payload.role
}
if (req.user && [...applicantDesignations ,...validatorDesignations].includes(req.user.designation)) {
next();
} else {
return res.status(401).json({message:"Access denied. Not a validator."});
}
})
};
export { verifyApplicantToken, verifyValidatorToken, verifyToken} ;

View File

@@ -0,0 +1,18 @@
import express from 'express';
import uploadFields from '../middleware/upload.js';
import { createApplication, updateApplication } from '../controllers/applicantControllers.js';
const router = express.Router();
router.post("/create-application",
uploadFields,
createApplication
);
router.put("/resubmit-application",
uploadFields,
updateApplication
);
export default router;

View File

@@ -0,0 +1,11 @@
import express from 'express';
import { applicantLogin, logout, validatorLogin } from '../controllers/authControllers.js';
const router = express.Router();
router.post('/applicant-login', applicantLogin);
router.post('/validator-login', validatorLogin);
router.get('/logout', logout)
export default router;

View File

@@ -0,0 +1,16 @@
import express from 'express';
import { dataRoot, getApplicationData, getApplicationsByStatus, getFile } from '../controllers/generalControllers.js';
const router = express.Router();
router.get("/dataRoot", dataRoot );
router.get('/getApplications/:status', getApplicationsByStatus);
router.get("/getApplicationData/:applicationId", getApplicationData);
router.get("/getFile/:applicationId/:fileName", getFile)
export default router;

View File

@@ -0,0 +1,16 @@
import express from 'express';
import {applicationAction, expenseAction, getApplicantNames, getReportData} from '../controllers/validatorController.js'
import multer from 'multer';
const router = express.Router();
const upload = multer();
router.put("/statusAction", upload.none(), applicationAction)
router.put("/expenseAction", expenseAction)
router.get("/getApplicantNames", getApplicantNames)
router.get("/getReportData", getReportData)
export default router;

9
backend/src/server.js Normal file
View File

@@ -0,0 +1,9 @@
import app from './app.js';
import dotenv from 'dotenv';
dotenv.config();
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
})

View File

@@ -0,0 +1,12 @@
import jwt from 'jsonwebtoken';
function generateToken(tokenObject) {
return jwt.sign(
tokenObject,
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
}
export default generateToken;

View File

@@ -0,0 +1,74 @@
import nodeMailer from 'nodemailer';
export default async function sendMail({ emailId, link, type, status, designation }) {
if (!process.env.TravelPolicyEmail || !process.env.TravelPolicyEmailPass) {
return;
}
console.log("parametrs", emailId, link, type, status, designation);
const transporter = nodeMailer.createTransport({
service: 'gmail',
port: 465,
secure: true,
logger: true,
debug: true,
secureConnection: false,
auth: {
user: process.env.TravelPolicyEmail,
pass: process.env.TravelPolicyEmailPass,
},
tls: {
rejectUnauthorized: true,
},
});
let mailOptions;
switch (type) {
case 'validator':
mailOptions = {
from: process.env.TravelPolicyEmail,
to: emailId,
subject: 'You have a new travel policy application to review',
html: `
<p>You have a new travel policy application to review. Please click on the link below to review the application:</p>
<a href=${link}>Review Application</a>
<p>Thank you.</p>
`,
};
break;
case 'applicant':
mailOptions = {
from: process.env.TravelPolicyEmail,
to: emailId,
subject: `Your travel policy application status: ${status}`,
html: `
<p>Your travel policy application has been ${status} by ${designation}. Please click on the link below to view the status of your application:</p>
<a href=${link}>View Application</a>
<p>Thank you.</p>
`,
};
break;
case 'accounts':
mailOptions = {
from: process.env.TravelPolicyEmail,
to: emailId,
subject: 'Transfer money to the applicant',
html: `
<p>Please transfer the travel policy amount to the applicant's account. Click on the link below to view the application:</p>`
}
break;
default:
throw new Error('Invalid email type');
}
try {
await transporter.sendMail(mailOptions);
console.log('Email sent successfully');
return true;
} catch (error) {
console.error('Error sending email:', error);
return { status: 'error', message: 'Verification code not sent' };
}
}