From b4adca13c7b1de1a969c3f2273b3a67b266b3567 Mon Sep 17 00:00:00 2001 From: Harikrishnan Gopal <118685394+hk151109@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:21:25 +0530 Subject: [PATCH] Commit --- client/src/Pages/CourseForm.css | 32 +++ client/src/Pages/CourseForm.jsx | 415 +++++++---------------------- client/src/Pages/CourseForm1.jsx | 392 +++++++++++++++++++++++++++ client/src/api.js | 121 +++++++-- server/models/Appointment.js | 10 +- server/routes/appointmentRoutes.js | 90 ++++--- server/server.js | 103 +++---- 7 files changed, 702 insertions(+), 461 deletions(-) create mode 100644 client/src/Pages/CourseForm1.jsx diff --git a/client/src/Pages/CourseForm.css b/client/src/Pages/CourseForm.css index 1d45e17..1099137 100644 --- a/client/src/Pages/CourseForm.css +++ b/client/src/Pages/CourseForm.css @@ -69,6 +69,38 @@ button[type="submit"] { width: 50%; } +.suggestions { + list-style-type: none; + margin: 0; + padding: 0; + border: 1px solid #ccc; + max-height: 150px; + overflow-y: auto; + background: #fff; + position: absolute; + width: 100%; + z-index: 10; +} + +.suggestions li { + padding: 8px; + cursor: pointer; +} + +.suggestions li:hover { + background-color: #f0f0f0; +} + +.error-message { + color: red; + font-size: 12px; +} + +.error input { + border-color: red; +} + + @media (max-width: 768px) { .form-container { margin: 20px; diff --git a/client/src/Pages/CourseForm.jsx b/client/src/Pages/CourseForm.jsx index 9236809..a78f3bb 100644 --- a/client/src/Pages/CourseForm.jsx +++ b/client/src/Pages/CourseForm.jsx @@ -1,194 +1,6 @@ -// import React, { useState, useEffect } from "react"; -// import { useLocation, useParams, useNavigate } from "react-router-dom"; -// import "./CourseForm.css"; - -// const CourseForm = () => { -// const { id } = useParams(); // Get the course ID from the URL params -// const location = useLocation(); -// const navigate = useNavigate(); // Updated for navigation -// const { course } = location.state || {}; - -// const [options, setOptions] = useState({ -// assessment: [], -// reassessment: [], -// paperSetting: [], -// moderation: [], -// pwdPaperSetter: [], -// oralsPracticals: [], // New field for Orals/Practicals -// }); - -// const [formData, setFormData] = useState({ -// assessment: "", -// reassessment: "", -// paperSetting: "", -// moderation: "", -// pwdPaperSetter: "", -// oralsPracticals: "", // New field for Orals/Practicals -// }); - -// const [errors, setErrors] = useState({}); // To track validation errors - -// // Fetch data for search bars -// useEffect(() => { -// const fetchOptions = async () => { -// try { -// const response = await fetch("/api/options"); // Replace with your API endpoint -// const data = await response.json(); -// setOptions(data); -// } catch (error) { -// console.error("Failed to fetch options:", error); -// } -// }; - -// fetchOptions(); -// }, []); - -// const handleInputChange = (e) => { -// const { name, value } = e.target; -// setFormData({ ...formData, [name]: value }); -// }; - -// const validateForm = () => { -// const newErrors = {}; -// Object.keys(formData).forEach((field) => { -// if (!formData[field]) { -// newErrors[field] = "This field is required"; -// } -// }); -// setErrors(newErrors); -// return Object.keys(newErrors).length === 0; -// }; - -// const handleSubmit = (e) => { -// e.preventDefault(); -// if (validateForm()) { -// console.log("Form submitted:", formData); - -// navigate("/courses", { state: { updatedCourse: { ...course, status: "Submitted" } } }); -// } -// }; - -// return ( -//
-//

Course Info

-//
-// -// -// -// -// -// -// -// -// -//
-//
-// ); -// }; - -// export default CourseForm; - -// CourseForm.jsx - import React, { useState, useEffect } from "react"; import { useLocation, useParams, useNavigate } from "react-router-dom"; -import { fetchFaculties } from "../api"; +import { fetchFaculties, saveAppointment } from "../api"; import "./CourseForm.css"; const CourseForm = () => { @@ -198,35 +10,27 @@ const CourseForm = () => { const { course } = location.state || {}; const [options, setOptions] = useState({ - assessment: [], - reassessment: [], - paperSetting: [], - moderation: [], - pwdPaperSetter: [], - oralsPracticals: [], - faculties: [], // New field for faculties + faculties: [], // List of all faculties }); + const [suggestions, setSuggestions] = useState({}); // To hold suggestions for each field const [formData, setFormData] = useState({ + oralsPracticals: "", assessment: "", reassessment: "", paperSetting: "", moderation: "", pwdPaperSetter: "", - oralsPracticals: "", // New field for Orals/Practicals }); const [errors, setErrors] = useState({}); + // Fetch faculty list on mount useEffect(() => { const fetchOptionsAndFaculties = async () => { try { - const facultiesData = await fetchFaculties(); // Fetch faculty names from the backend - console.log(facultiesData); - setOptions(prevOptions => ({ - ...prevOptions, - faculties: facultiesData, - })); + const facultiesData = await fetchFaculties(); // Fetch faculty names from backend + setOptions((prev) => ({ ...prev, faculties: facultiesData })); } catch (error) { console.error("Failed to fetch faculties:", error); } @@ -235,11 +39,25 @@ const CourseForm = () => { fetchOptionsAndFaculties(); }, []); + // Handle input changes for form fields const handleInputChange = (e) => { const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + + // Filter suggestions for the current field + if (options.faculties.length > 0) { + const filteredSuggestions = options.faculties.filter((faculty) => + faculty.name.toLowerCase().includes(value.toLowerCase()) + ); + setSuggestions((prev) => ({ + ...prev, + [name]: filteredSuggestions, + })); + } }; + // Validate the form const validateForm = () => { const newErrors = {}; Object.keys(formData).forEach((field) => { @@ -251,29 +69,49 @@ const CourseForm = () => { return Object.keys(newErrors).length === 0; }; - // const handleSubmit = (e) => { - // e.preventDefault(); - // if (validateForm()) { - // console.log("Form submitted:", formData); - // navigate("/courses", { state: { updatedCourse: { ...course, status: "Submitted" } } }); - // } - // }; - const handleSubmit = (e) => { - e.preventDefault(); + // Handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); // Prevent default form submission behavior if (validateForm()) { - console.log("Form submitted:", formData); - navigate("/courses", { - state: { - updatedCourse: { - ...course, - status: "Submitted", // Update status - ...formData, // Include form data if required + try { + const groupedTasks = {}; + + // Group tasks by facultyId + Object.entries(formData).forEach(([field, value]) => { + const assignedFaculty = options.faculties.find( + (faculty) => faculty.name === value + ); + if (assignedFaculty) { + if (!groupedTasks[assignedFaculty.facultyId]) { + groupedTasks[assignedFaculty.facultyId] = { + facultyId: assignedFaculty.facultyId, + courseId: course?.id || id, + tasks: [], + }; + } + groupedTasks[assignedFaculty.facultyId].tasks.push(field); + } + }); + + const payload = Object.values(groupedTasks); // Convert the grouped tasks into an array + console.log("Saving appointment with payload:", payload); + await saveAppointment(payload); // Save to backend + console.log("Form submitted successfully:", payload); + + // Redirect to courses page after successful submission + navigate("/courses", { + state: { + updatedCourse: { + ...course, + status: "Submitted", + }, }, - }, - }); + }); + } catch (error) { + console.error("Failed to save appointment:", error); + } } }; - return (
@@ -287,103 +125,42 @@ const CourseForm = () => { Course Name: - - - - - - - + {[ + { name: "oralsPracticals", label: "Orals/Practicals" }, + { name: "assessment", label: "Assessment" }, + { name: "reassessment", label: "Reassessment" }, + { name: "paperSetting", label: "Paper Setting" }, + { name: "moderation", label: "Moderation" }, + { name: "pwdPaperSetter", label: "PwD Paper Setter" }, + ].map(({ name, label }) => ( +
+ + {errors[name] && {errors[name]}} +
+ ))} +
); diff --git a/client/src/Pages/CourseForm1.jsx b/client/src/Pages/CourseForm1.jsx new file mode 100644 index 0000000..9236809 --- /dev/null +++ b/client/src/Pages/CourseForm1.jsx @@ -0,0 +1,392 @@ +// import React, { useState, useEffect } from "react"; +// import { useLocation, useParams, useNavigate } from "react-router-dom"; +// import "./CourseForm.css"; + +// const CourseForm = () => { +// const { id } = useParams(); // Get the course ID from the URL params +// const location = useLocation(); +// const navigate = useNavigate(); // Updated for navigation +// const { course } = location.state || {}; + +// const [options, setOptions] = useState({ +// assessment: [], +// reassessment: [], +// paperSetting: [], +// moderation: [], +// pwdPaperSetter: [], +// oralsPracticals: [], // New field for Orals/Practicals +// }); + +// const [formData, setFormData] = useState({ +// assessment: "", +// reassessment: "", +// paperSetting: "", +// moderation: "", +// pwdPaperSetter: "", +// oralsPracticals: "", // New field for Orals/Practicals +// }); + +// const [errors, setErrors] = useState({}); // To track validation errors + +// // Fetch data for search bars +// useEffect(() => { +// const fetchOptions = async () => { +// try { +// const response = await fetch("/api/options"); // Replace with your API endpoint +// const data = await response.json(); +// setOptions(data); +// } catch (error) { +// console.error("Failed to fetch options:", error); +// } +// }; + +// fetchOptions(); +// }, []); + +// const handleInputChange = (e) => { +// const { name, value } = e.target; +// setFormData({ ...formData, [name]: value }); +// }; + +// const validateForm = () => { +// const newErrors = {}; +// Object.keys(formData).forEach((field) => { +// if (!formData[field]) { +// newErrors[field] = "This field is required"; +// } +// }); +// setErrors(newErrors); +// return Object.keys(newErrors).length === 0; +// }; + +// const handleSubmit = (e) => { +// e.preventDefault(); +// if (validateForm()) { +// console.log("Form submitted:", formData); + +// navigate("/courses", { state: { updatedCourse: { ...course, status: "Submitted" } } }); +// } +// }; + +// return ( +//
+//

Course Info

+//
+// +// +// +// +// +// +// +// +// +//
+//
+// ); +// }; + +// export default CourseForm; + +// CourseForm.jsx + +import React, { useState, useEffect } from "react"; +import { useLocation, useParams, useNavigate } from "react-router-dom"; +import { fetchFaculties } from "../api"; +import "./CourseForm.css"; + +const CourseForm = () => { + const { id } = useParams(); + const location = useLocation(); + const navigate = useNavigate(); + const { course } = location.state || {}; + + const [options, setOptions] = useState({ + assessment: [], + reassessment: [], + paperSetting: [], + moderation: [], + pwdPaperSetter: [], + oralsPracticals: [], + faculties: [], // New field for faculties + }); + + const [formData, setFormData] = useState({ + assessment: "", + reassessment: "", + paperSetting: "", + moderation: "", + pwdPaperSetter: "", + oralsPracticals: "", // New field for Orals/Practicals + }); + + const [errors, setErrors] = useState({}); + + useEffect(() => { + const fetchOptionsAndFaculties = async () => { + try { + const facultiesData = await fetchFaculties(); // Fetch faculty names from the backend + console.log(facultiesData); + setOptions(prevOptions => ({ + ...prevOptions, + faculties: facultiesData, + })); + } catch (error) { + console.error("Failed to fetch faculties:", error); + } + }; + + fetchOptionsAndFaculties(); + }, []); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const validateForm = () => { + const newErrors = {}; + Object.keys(formData).forEach((field) => { + if (!formData[field]) { + newErrors[field] = "This field is required"; + } + }); + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + // const handleSubmit = (e) => { + // e.preventDefault(); + // if (validateForm()) { + // console.log("Form submitted:", formData); + // navigate("/courses", { state: { updatedCourse: { ...course, status: "Submitted" } } }); + // } + // }; + const handleSubmit = (e) => { + e.preventDefault(); + if (validateForm()) { + console.log("Form submitted:", formData); + navigate("/courses", { + state: { + updatedCourse: { + ...course, + status: "Submitted", // Update status + ...formData, // Include form data if required + }, + }, + }); + } + }; + + + return ( +
+

Course Info

+
+ + + + + + + + + +
+
+ ); +}; + +export default CourseForm; diff --git a/client/src/api.js b/client/src/api.js index 4fd679b..f871349 100644 --- a/client/src/api.js +++ b/client/src/api.js @@ -1,55 +1,122 @@ const BASE_URL = "http://localhost:8080/api"; -export const fetchCourses = async (filterData) => { +// Helper function for handling fetch requests +const fetchData = async (url, options) => { + try { + const response = await fetch(url, options); + + // Check if response is OK (status 200-299) + if (!response.ok) { + let errorDetails = {}; + try { + errorDetails = await response.json(); // Attempt to parse error response + } catch (err) { + console.warn("Failed to parse error details:", err); + } + throw new Error( + `Error: ${response.statusText} (${response.status}) - ${ + errorDetails.message || "No details available" + }` + ); + } + + // Return JSON response if successful + return await response.json(); + } catch (error) { + console.error(`Request failed for ${url}:`, error.message); + throw error; // Re-throw for the caller to handle + } +}; + +// Fetch courses with optional filters +export const fetchCourses = async (filterData = {}) => { try { - - // Serialize filterData into query parameters const queryString = new URLSearchParams(filterData).toString(); - // console.log(queryString); - const response = await fetch(`${BASE_URL}/courses?${queryString}`, { + const url = `${BASE_URL}/courses?${queryString}`; + return fetchData(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); - - if (!response.ok) { - throw new Error("Failed to fetch courses"); - } - - const filteredCourses = await response.json(); - console.log(filteredCourses); - return filteredCourses; } catch (error) { - console.error("Error fetching courses:", error); - throw error; // Re-throw error to be handled by the caller + console.error("Error fetching courses:", error.message); + throw error; } }; +// Fetch list of faculties export const fetchFaculties = async () => { try { - const response = await fetch(`${BASE_URL}/faculty`); - if (!response.ok) { - throw new Error(`Failed to fetch faculties: ${response.statusText}`); - } - const data = await response.json(); - return data; + const url = `${BASE_URL}/faculty`; + return fetchData(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); } catch (error) { console.error("Error fetching faculties:", error.message); throw error; } }; +// Fetch available options for form dropdowns export const fetchOptions = async () => { try { - const response = await fetch(`${BASE_URL}/options`); - if (!response.ok) { - throw new Error(`Failed to fetch options: ${response.statusText}`); - } - const data = await response.json(); - return data; + const url = `${BASE_URL}/options`; + return fetchData(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); } catch (error) { console.error("Error fetching options:", error.message); throw error; } }; + +// Save multiple appointments to MongoDB +export const saveAppointment = async (appointmentsData) => { + console.log("Saving appointments with payload:", appointmentsData); + + // Validate input format + if (!Array.isArray(appointmentsData) || appointmentsData.length === 0) { + const errorMessage = + "Invalid or missing appointment data: expected a non-empty array"; + console.error(errorMessage); + throw new Error(errorMessage); + } + + // Validate each appointment's structure + const invalidEntries = appointmentsData.filter( + (appointment) => + !appointment.facultyId || !appointment.courseId || !Array.isArray(appointment.tasks) + ); + + if (invalidEntries.length > 0) { + const errorMessage = `Invalid appointments detected: ${JSON.stringify( + invalidEntries + )}`; + console.error(errorMessage); + throw new Error(errorMessage); + } + + try { + const url = `${BASE_URL}/appointments`; + const response = await fetchData(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ appointments: appointmentsData }), // Send appointments as an array + }); + + console.log("Appointments saved successfully:", response); + return response; + } catch (error) { + console.error("Error saving appointments:", error.message); + throw error; + } +}; diff --git a/server/models/Appointment.js b/server/models/Appointment.js index 49c45fd..abc6752 100644 --- a/server/models/Appointment.js +++ b/server/models/Appointment.js @@ -1,11 +1,11 @@ const mongoose = require("mongoose"); -const { v4: uuidv4 } = require("uuid"); // For UUID generation const AppointmentSchema = new mongoose.Schema({ - appointmentId: { type: String, default: uuidv4 }, // Auto-generate using UUID - courseId: { type: String, required: true, ref: "Course" }, - facultyId: { type: String, required: true, ref: "Faculty" }, - appointmentType: { type: [String], required: true }, // Array of appointment types + facultyId: { type: String, required: true }, + facultyName: { type: String, required: true }, + courseId: { type: String, required: true }, + courseName: { type: String, required: true }, + task: { type: String, required: true }, }); module.exports = mongoose.model("Appointment", AppointmentSchema); diff --git a/server/routes/appointmentRoutes.js b/server/routes/appointmentRoutes.js index ffce999..8b50f46 100644 --- a/server/routes/appointmentRoutes.js +++ b/server/routes/appointmentRoutes.js @@ -1,51 +1,65 @@ const express = require("express"); -const Appointment = require("../models/Appointment"); - const router = express.Router(); +const Appointment = require("../models/Appointment"); +const Faculty = require("../models/Faculty"); +const Course = require("../models/Course"); + +// Save multiple appointments +router.post("/", async (req, res) => { + try { + const { appointments } = req.body; // Expecting an array of appointments + if (!appointments || !Array.isArray(appointments)) { + return res.status(400).json({ error: "Invalid or missing data" }); + } + + const savedAppointments = []; + for (const appointment of appointments) { + const { facultyId, courseId, tasks } = appointment; + + // Validate input data + if (!facultyId || !courseId || !Array.isArray(tasks) || tasks.length === 0) { + return res.status(400).json({ error: "Invalid appointment data" }); + } + + const faculty = await Faculty.findOne({ facultyId }); + const course = await Course.findOne({ courseId }); + + if (!faculty || !course) { + return res.status(404).json({ + error: `Faculty or Course not found for facultyId: ${facultyId}, courseId: ${courseId}`, + }); + } + + // Save each task as a separate appointment + for (const task of tasks) { + const newAppointment = new Appointment({ + facultyId, + facultyName: faculty.name, + courseId, + courseName: course.name, + task, + }); + const savedAppointment = await newAppointment.save(); + savedAppointments.push(savedAppointment); + } + } + + res.status(201).json(savedAppointments); + } catch (error) { + console.error("Error saving appointments:", error); + res.status(500).json({ error: "Failed to save appointments" }); + } +}); // Get all appointments router.get("/", async (req, res) => { try { - const appointments = await Appointment.find() - .populate("courseId", "name department") - .populate("facultyId", "name email"); + const appointments = await Appointment.find(); res.json(appointments); } catch (error) { + console.error("Error fetching appointments:", error); res.status(500).json({ error: "Failed to fetch appointments" }); } }); -// Create a new appointment -router.post("/", async (req, res) => { - try { - const { courseId, facultyId, appointmentType } = req.body; - - const newAppointment = new Appointment({ - courseId, - facultyId, - appointmentType, - }); - - await newAppointment.save(); - res.status(201).json(newAppointment); - } catch (error) { - res.status(400).json({ error: "Failed to create appointment" }); - } - }); - -// Get appointment by ID -router.get("/:id", async (req, res) => { - try { - const appointment = await Appointment.findOne({ appointmentId: req.params.id }) - .populate("courseId", "name department") - .populate("facultyId", "name email"); - if (!appointment) { - return res.status(404).json({ error: "Appointment not found" }); - } - res.json(appointment); - } catch (error) { - res.status(500).json({ error: "Failed to fetch appointment" }); - } -}); - module.exports = router; diff --git a/server/server.js b/server/server.js index 633d5db..554f761 100644 --- a/server/server.js +++ b/server/server.js @@ -1,101 +1,58 @@ const express = require("express"); const mongoose = require("mongoose"); -const bodyParser = require("body-parser"); const cors = require("cors"); -require("dotenv").config(); -const passport = require("passport"); const session = require("express-session"); -const bcrypt = require("bcryptjs"); -const LocalStrategy = require("passport-local").Strategy; -const crypto = require("crypto"); -const jwt = require("jsonwebtoken"); +const passport = require("passport"); +const bodyParser = require("body-parser"); const path = require("path"); +const bcrypt = require("bcryptjs"); +require("dotenv").config(); -const User = require("./models/User"); -const PasswordRouter = require("./routes/authRoutes"); +// Import Routes +const authRoutes = require("./routes/authRoutes"); const courseRoutes = require("./routes/courseRoutes"); const facultyRoutes = require("./routes/facultyRoutes"); const appointmentRoutes = require("./routes/appointmentRoutes"); const optionsRoutes = require("./routes/optionsRoutes"); -// Existing Database Connection -const { connectdb } = require("./ConnectionDb"); -connectdb(); - // MongoDB Connection mongoose - .connect(process.env.mongoURI) + .connect(process.env.mongoURI, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log("MongoDB connected")) - .catch((err) => console.error("MongoDB connection error:", err)); + .catch((err) => { + console.error("MongoDB connection error:", err); + process.exit(1); // Exit the app if the database connection fails + }); +// Initialize App const app = express(); +const PORT = 8080; // Middleware -app.use(cors()); +app.use(cors({ origin: "http://localhost:3000", credentials: true })); app.use(express.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use( session({ - secret: "secret", + secret: "secret", // This can be replaced with another secret from .env if required resave: false, saveUninitialized: false, }) ); - app.use(passport.initialize()); app.use(passport.session()); -// CORS configuration -app.use( - cors({ - origin: "http://localhost:3000", - credentials: true, - }) -); - -// Passport Configuration +// Passport Config require("./config/passport"); -passport.use( - new LocalStrategy( - { usernameField: "email" }, - async (email, password, done) => { - try { - const user = await User.findOne({ email }); - if (!user) { - return done(null, false, { message: "Incorrect email" }); - } - const isMatch = await bcrypt.compare(password, user.password); - if (isMatch) { - return done(null, user); - } else { - return done(null, false, { message: "Incorrect password" }); - } - } catch (error) { - return done(error); - } - } - ) -); - -passport.serializeUser((user, done) => { - done(null, user.id); // Store user ID in the session -}); - -passport.deserializeUser((id, done) => { - User.findById(id, (err, user) => { - done(err, user); - }); -}); - // Routes -app.use("/password", PasswordRouter); +app.use("/password", authRoutes); app.use("/api/courses", courseRoutes); app.use("/api/faculty", facultyRoutes); -app.use("/api/appointments", appointmentRoutes); +app.use("/api/appointments", appointmentRoutes); // Appointment route handles the updated structure app.use("/api/options", optionsRoutes); -// OAuth Routes +// Google OAuth Routes app.get( "/auth/google", passport.authenticate("google", { scope: ["profile", "email"] }) @@ -104,11 +61,12 @@ app.get( app.get( "/auth/google/callback", passport.authenticate("google", { failureRedirect: "/" }), - function (req, res) { - res.redirect("http://localhost:3000/Welcom"); + (req, res) => { + res.redirect("http://localhost:3000/Welcom"); // Redirect to a frontend route after successful login } ); +// Local authentication routes (register and login) app.post("/api/register", async (req, res) => { try { const { username, email, password } = req.body; @@ -180,6 +138,7 @@ app.get("/auth/logout", function (req, res) { }); }); +// User Profile Route app.get("/api/user/profile", async (req, res) => { try { if (req.user) { @@ -193,19 +152,19 @@ app.get("/api/user/profile", async (req, res) => { } }); -// Serve Static Files +// Serve React Build Files app.use(express.static(path.join(__dirname, "../client/build"))); - -// Catch-All Route app.get("*", (req, res) => res.sendFile(path.join(__dirname, "../client/build/index.html")) ); - - +// Error Handling Middleware +app.use((err, req, res, next) => { + console.error("Error:", err.stack); + res.status(err.status || 500).json({ error: err.message || "Internal Server Error" }); +}); // Start Server -const Port = process.env.PORT || 8080; -app.listen(Port, () => { - console.log(`Server is Running at port ${Port}`); +app.listen(PORT, () => { + console.log(`Server is running at http://localhost:8080`); });