This commit is contained in:
Leo 2024-01-19 20:22:41 +08:00
parent 47aaf71d10
commit 60c132d9ea
7 changed files with 243 additions and 19 deletions

View File

@ -1,4 +1,4 @@
const mysql = require("mysql2");
const path = require("path"); const path = require("path");
require('dotenv').config({ path: path.resolve(__dirname, '../.env') }) require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
const fs = require('fs'); const fs = require('fs');

View File

@ -1,7 +1,7 @@
const express = require("express"); const express = require("express");
const session = require("express-session"); const session = require("express-session");
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const mysql2 = require('mysql2');
const bodyParser = require("body-parser"); const bodyParser = require("body-parser");
const bcrypt = require("bcrypt"); const bcrypt = require("bcrypt");
const crypto = require("crypto"); const crypto = require("crypto");
@ -13,7 +13,7 @@ const { format } = require('date-fns');
const { Sequelize } = require('sequelize'); const { Sequelize } = require('sequelize');
const { transporter } = require("./modules/nodeMailer"); const { transporter } = require("./modules/nodeMailer");
const { connection } = require("./modules/mysql");
const { sequelize, User } = require("./modules/mysql"); const { sequelize, User } = require("./modules/mysql");
const userLogs= require('./models/userLogs')(sequelize); // Adjust the path based on your project structure const userLogs= require('./models/userLogs')(sequelize); // Adjust the path based on your project structure
const app = express(); const app = express();
@ -80,8 +80,6 @@ app.get("/login", (req, res) => {
res.render("login", { error: null }); res.render("login", { error: null });
}); });
const limiter = rateLimit({ const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 3 requests per windowMs max: 5, // limit each IP to 3 requests per windowMs
@ -168,7 +166,7 @@ app.post("/verify-otp", [
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return res.render('otp', { error: 'Invalid OTP. Please try again.', username: req.body.username, csrfToken: req.session.csrfToken }); return res.render('otp', { error: 'Invalid OTP. Please try again.'});
} }
const enteredOTP = req.body.otp; const enteredOTP = req.body.otp;
@ -223,12 +221,7 @@ app.post("/verify-otp", [
app.get("/logout", async (req, res) => { app.get("/logout", async (req, res) => {
try { try {
const username = req.session.username || "Unknown User"; const username = req.session.username ;
// Log the logout activity using Sequelize
await userLogs.create({ username, activity: "User logged out. Session destroyed." });
// Log the user out by clearing the session // Log the user out by clearing the session
req.session.destroy(async (err) => { req.session.destroy(async (err) => {
if (err) { if (err) {
@ -238,7 +231,8 @@ app.post("/verify-otp", [
await userLogs.create({ username, activity: "User logged out unsuccessfully. Session not destroyed." }); await userLogs.create({ username, activity: "User logged out unsuccessfully. Session not destroyed." });
} else { } else {
console.log("Session destroyed."); console.log("Session destroyed.");
// Log the logout activity using Sequelize
await userLogs.create({ username, activity: "User logged out. Session destroyed." });
// Clear the session token cookie // Clear the session token cookie
res.clearCookie('sessionToken'); res.clearCookie('sessionToken');
} }

View File

@ -1,6 +1,102 @@
img { body {
margin: 0;
font-family: Arial, sans-serif;
}
#map {
display: block; display: block;
margin: auto; margin: auto;
max-width: 100%; max-width: 100%;
max-height: 100vh; max-height: 100vh;
} }
#map-container {
position: relative;
}
#north {
top: 25%;
left: 50%;
transform: translateX(-50%);
}
#south {
top: 60%;
left: 50%;
transform: translateX(-50%);
}
#east {
top: 50%;
left: 60%;
transform: translateX(-50%) translateY(-50%);
}
#west {
top: 50%;
left: 40%;
transform: translateX(-50%) translateY(-50%);
}
#central {
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.info-box {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
font-size: 12px;
}
#north h3 {
color: #34d0db;
}
#south h3 {
color: #2ecc71;
}
#east h3 {
color: #3498db;
}
#west h3 {
color: #e74c3c;
}
.additional-info-box {
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
margin: 20px auto; /* Center horizontally */
max-width: 300px auto; /* Adjust the maximum width as needed */
text-align: center; /* Center text horizontally */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.info-item {
margin-bottom: 10px;
}
.info-label {
font-weight: bold;
margin-right: 10px;
}
.info-value {
color: #007bff; /* Adjust the color as needed */
}
.info-box.active {
background-color: #213f6d;
color: #fff;
}

View File

@ -0,0 +1,87 @@
document.addEventListener("DOMContentLoaded", function () {
function updateAdditionalInfo(region) {
const infoContainer = document.getElementById("additional-info");
// Replace the following with actual data retrieval based on the region
const aqi = "15";
const temperature = "25°C";
const humidity = "60%";
infoContainer.innerHTML = `
<div class="additional-info-box">
<h3>Additional Information - ${region}</h3>
<div class="info-item">
<span class="info-label">Air Quality Index:</span>
<span class="info-value">${aqi}</span>
</div>
<div class="info-item">
<span class="info-label">Temperature:</span>
<span class="info-value">${temperature}</span>
</div>
<div class="info-item">
<span class="info-label">Humidity:</span>
<span class="info-value">${humidity}</span>
</div>
</div>
`;
// Remove the 'active' class from all info-box elements
const infoBoxes = document.querySelectorAll('.info-box');
infoBoxes.forEach(box => box.classList.remove('active'));
// Add the 'active' class to the clicked info-box
const clickedBox = document.getElementById(region.toLowerCase());
clickedBox.classList.add('active');
}
const defaultRegion = "North";
const defaultBox = document.getElementById(defaultRegion.toLowerCase());
defaultBox.classList.add('active');
const defaultAqi = "--"; // Replace with actual data retrieval
updateAdditionalInfo(defaultRegion, defaultAqi);
// Event listeners for each region's info-box
document.getElementById("north").addEventListener("click", function () {
const northAqi = "--"; // Replace with actual data retrieval
updateAdditionalInfo("North", northAqi);
});
document.getElementById("south").addEventListener("click", function () {
const southAqi = "--";
updateAdditionalInfo("South", southAqi);
});
document.getElementById("east").addEventListener("click", function () {
const eastAqi = "--";
updateAdditionalInfo("East", eastAqi);
});
document.getElementById("west").addEventListener("click", function () {
const westAqi = "--";
updateAdditionalInfo("West", westAqi);
});
document.getElementById("central").addEventListener("click", function () {
const centralAqi = "--";
updateAdditionalInfo("Central", centralAqi);
});
});
// Event listeners for each region's info-box
document.getElementById("north").addEventListener("click", function () {
updateAdditionalInfo("North");
});
document.getElementById("south").addEventListener("click", function () {
updateAdditionalInfo("South");
});
document.getElementById("east").addEventListener("click", function () {
updateAdditionalInfo("East");
});
document.getElementById("west").addEventListener("click", function () {
updateAdditionalInfo("West");
});
document.getElementById("central").addEventListener("click", function () {
updateAdditionalInfo("Central");
});

View File

@ -0,0 +1,19 @@
function searchFunction() {
// Get the search input value
var searchTerm = document.getElementById('searchInput').value.toLowerCase();
// Get all blog entries
var blogEntries = document.getElementById('blogEntries').getElementsByClassName('card');
// Loop through each blog entry and hide/show based on the search term
for (var i = 0; i < blogEntries.length; i++) {
var title = blogEntries[i].getElementsByClassName('card-title')[0].innerText.toLowerCase();
var text = blogEntries[i].getElementsByClassName('card-text')[0].innerText.toLowerCase();
if (title.includes(searchTerm) || text.includes(searchTerm)) {
blogEntries[i].style.display = 'block';
} else {
blogEntries[i].style.display = 'none';
}
}
}

View File

@ -54,10 +54,37 @@
<br> <br>
<br> <br>
<div class="map"> <div id="map-container">
<img src="images/map.png" alt="Map Image"> <img src="images/map.png" alt="Singapore Map" id="map">
<!-- Information boxes for each region -->
<div class="info-box" id="north">
<h3>North</h3>
<p>Air Quality Index: 15 <span id="north-aqi"></span></p>
</div> </div>
<div class="info-box" id="south">
<h3>South</h3>
<p>Air Quality Index: 16 <span id="south-aqi"></span></p>
</div>
<div class="info-box" id="east">
<h3>East</h3>
<p>Air Quality Index: 16 <span id="east-aqi"></span></p>
</div>
<div class="info-box" id="west">
<h3>West</h3>
<p>Air Quality Index: 18 <span id="west-aqi"></span></p>
</div>
<div class="info-box" id="central">
<h3>Central</h3>
<p>Air Quality Index: 17 <span id="central-aqi"></span></p>
</div>
</div>
<div id="additional-info"></div>
<br> <br>
<br> <br>
@ -148,6 +175,7 @@
<!-- Bootstrap core JavaScript --> <!-- Bootstrap core JavaScript -->
<script src="vendor/jquery/jquery.min.js"></script> <script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="js/learnmore.js"></script>
</body> </body>
</html> </html>