forked from CSI-KJSCE/Travel-policy-
code base
This commit is contained in:
2
backend/.gitignore
vendored
Normal file
2
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
.env
|
||||
1518
backend/package-lock.json
generated
Normal file
1518
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
backend/package.json
Normal file
31
backend/package.json
Normal 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": ""
|
||||
}
|
||||
101
backend/prisma/schema.prisma
Normal file
101
backend/prisma/schema.prisma
Normal 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
123
backend/prisma/seed.js
Normal 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
29
backend/src/app.js
Normal 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;
|
||||
16
backend/src/config/designations.js
Normal file
16
backend/src/config/designations.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const applicantDesignations = [
|
||||
'STUDENT',
|
||||
'FACULTY',
|
||||
'HOD',
|
||||
'HOI',
|
||||
];
|
||||
|
||||
const validatorDesignations = [
|
||||
'FACULTY',
|
||||
'HOD',
|
||||
'HOI',
|
||||
'VC',
|
||||
'ACCOUNTS',
|
||||
];
|
||||
|
||||
export {applicantDesignations, validatorDesignations}
|
||||
5
backend/src/config/prismaConfig.js
Normal file
5
backend/src/config/prismaConfig.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default prisma;
|
||||
434
backend/src/controllers/applicantControllers.js
Normal file
434
backend/src/controllers/applicantControllers.js
Normal 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 };
|
||||
130
backend/src/controllers/authControllers.js
Normal file
130
backend/src/controllers/authControllers.js
Normal 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 };
|
||||
115
backend/src/controllers/formatter.js
Normal file
115
backend/src/controllers/formatter.js
Normal 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: "",
|
||||
};
|
||||
439
backend/src/controllers/generalControllers.js
Normal file
439
backend/src/controllers/generalControllers.js
Normal 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 };
|
||||
437
backend/src/controllers/validatorController.js
Normal file
437
backend/src/controllers/validatorController.js
Normal 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 };
|
||||
24
backend/src/middleware/upload.js
Normal file
24
backend/src/middleware/upload.js
Normal 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;
|
||||
97
backend/src/middleware/verifyJwt.js
Normal file
97
backend/src/middleware/verifyJwt.js
Normal 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} ;
|
||||
18
backend/src/routes/applicant.js
Normal file
18
backend/src/routes/applicant.js
Normal 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;
|
||||
11
backend/src/routes/auth.js
Normal file
11
backend/src/routes/auth.js
Normal 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;
|
||||
16
backend/src/routes/general.js
Normal file
16
backend/src/routes/general.js
Normal 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;
|
||||
16
backend/src/routes/validator.js
Normal file
16
backend/src/routes/validator.js
Normal 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
9
backend/src/server.js
Normal 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}`);
|
||||
})
|
||||
12
backend/src/services/generateToken.js
Normal file
12
backend/src/services/generateToken.js
Normal 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;
|
||||
74
backend/src/services/sendMail.js
Normal file
74
backend/src/services/sendMail.js
Normal 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' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user