Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@testing-library/user-event": "^13.5.0",
"axios": "^1.8.2",
"bootstrap": "^5.3.5",
"client": "file:",
"react": "^19.0.0",
"react-bootstrap": "^2.10.9",
"react-dom": "^19.0.0",
Expand Down Expand Up @@ -42,6 +43,6 @@
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
]
}
}
59 changes: 59 additions & 0 deletions client/src/pages/TokenRenewal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

const TokenRenewal = () => {
const { token } = useParams();
const [responseMessage, setResponseMessage] = useState('');
const [loading, setLoading] = useState(true);

useEffect(() => {
const renewToken = async () => {
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/api/token/renew`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
});

const data = await response.json();

if (response.ok) {
setResponseMessage({ text: '✅ Success: Your token has been renewed!', success: true });
} else {
setResponseMessage({ text: `❌ Error: ${data.message}`, success: false });
}
} catch (error) {
setResponseMessage({ text: '❌ Error: Unable to process your request.', success: false });
} finally {
setLoading(false);
}
};

if (token) {
renewToken();
} else {
setResponseMessage({ text: '❌ Error: No token found in the URL.', success: false });
setLoading(false);
}
}, [token]);

return (
<div className="token-renewal-container">
<h1>Token Renewal</h1>
{loading ? (
<p>⏳ Processing your token renewal...</p>
) : (
<div
className={`response-message ${responseMessage.success ? 'success' : 'error'}`}
style={{ marginTop: '20px' }}
>
{responseMessage.text}
</div>
)}
</div>
);
};

export default TokenRenewal;
5 changes: 5 additions & 0 deletions client/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import A4PresentationEvaluationForm from "./pages/A4PresentationEvaluationForm";
import SupervisorDashboard from "./pages/SupervisorDashboard";
import CoordinatorDashboard from "./pages/CoordinatorDashboard";
import CoordinatorRequestDetailView from "./pages/CoordinatorRequestDetailView";
import TokenRenewal from "./pages/TokenRenewal";

// Create and export the router configuration
const router = createBrowserRouter([
Expand Down Expand Up @@ -65,6 +66,10 @@ const router = createBrowserRouter([
path: "coordinator/request/:id",
element: <CoordinatorRequestDetailView />,
},
{
path: "renew-token/:token",
element: <TokenRenewal />,
},
],
},
]);
Expand Down
2 changes: 2 additions & 0 deletions server/jobs/cronJobsConfig.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const CronJob = require("../models/CronJob");
const { coordinatorReminder, supervisorReminder } = require("./reminderEmail");
const { checkAndSendReminders } = require("./tokenExpiryCheck");

// Map of job names to their corresponding functions
const jobFunctions = {
coordinatorApprovalReminder: coordinatorReminder,
supervisorApprovalReminder: supervisorReminder,
tokenExpiryReminder: checkAndSendReminders,
// Add more job functions here as needed
};

Expand Down
57 changes: 57 additions & 0 deletions server/jobs/tokenExpiryCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const mongoose = require("mongoose");
const UserTokenRequest = require("../models/TokenRequest"); // Adjust the path if needed
const emailService = require("../services/emailService");
require("dotenv").config();

const checkAndSendReminders = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
const now = new Date();

// Tokens expiring in 3 and 7 days
const daysToCheck = [3, 7];

for (const days of daysToCheck) {
const targetDate = new Date(now);
targetDate.setDate(now.getDate() + days);

const expiringTokens = await UserTokenRequest.find({
isActivated: true,
status: "activated",
expiresAt: {
$gte: new Date(targetDate.setHours(0, 0, 0, 0)),
$lt: new Date(targetDate.setHours(23, 59, 59, 999)),
},
});

for (const token of expiringTokens) {
const subject = `Reminder: Your Internship Token Expires in ${days} Day${days > 1 ? "s" : ""}`;
const renewalLink = `${process.env.FRONTEND_URL}/renew-token/${token.token}`; // Update with actual renewal path

const html = `
<p>Hello ${token.fullName},</p>
<p>This is a reminder that your internship access token will expire in <strong>${days} day${days > 1 ? "s" : ""}</strong> on <strong>${token.expiresAt.toDateString()}</strong>.</p>
<p>If your token expires, you will lose access to the internship management system.</p>
<p><a href="${renewalLink}">Click here to renew your token securely</a>.</p>
<p>Thank you,<br/>Internship Program Management Team</p>
`;

const emailResponse = await emailService.sendEmail({
to: token.ouEmail,
subject,
html,
});

console.log(`Email sent to ${token.ouEmail} for ${days}-day reminder:`, emailResponse);
}
}

await mongoose.disconnect();
} catch (error) {
console.error("Error during token expiry check:", error);
}
}

module.exports = {
checkAndSendReminders,
};
3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"mongodb": "^6.14.2",
"mongoose": "^8.13.2",
"node-cron": "^3.0.3",
"nodemailer": "^6.10.0"
"nodemailer": "^6.10.0",
"server": "file:"
},
"devDependencies": {
"jest": "^29.7.0"
Expand Down
38 changes: 38 additions & 0 deletions server/routes/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,42 @@ router.delete("/deactivate", async (req, res) => {
}
});

router.post('/renew', async (req, res) => {
const { token } = req.body;

try {

if (!token) return res.status(400).json({ error: "Token is missing." });
console.log("Received token:", token);
const userToken = await TokenRequest.findOne({ token: token });

if (!userToken) {
return res.status(404).json({ message: 'Token not found or invalid.' });
}

if (userToken.status !== 'activated') {
return res.status(400).json({ message: 'Token is not activated, cannot renew.' });
}

// Renew the token logic (extend expiry, etc.)
const currentDate = new Date();
userToken.expiresAt = new Date(currentDate.setMonth(currentDate.getMonth() + 6));
await userToken.save();

// Send confirmation email (optional)
await emailService.sendEmail({
to: userToken.ouEmail,
subject: 'Your Token Has Been Renewed',
html: `<p>Your token has been successfully renewed and will now expire on ${userToken.expiresAt.toLocaleDateString()}.</p>`,
});

console.log('Token successfully renewed!')
return res.status(200).json({ message: 'Token successfully renewed!' });

} catch (error) {
console.error('Error renewing token:', error);
return res.status(500).json({ message: 'An error occurred while renewing the token.' });
}
});

module.exports = router;