From 63fa189b56f5f747c479c6da8785aca25501aa43 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 18:49:21 +0530 Subject: [PATCH 01/31] fix(backend): stop leaking password hash and salt in auth responses (cherry picked from commit 6ecf27b9e468673e54105ca5e616467dbc912046) --- backend/models/schema.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/models/schema.js b/backend/models/schema.js index 4211276e..7c224c41 100644 --- a/backend/models/schema.js +++ b/backend/models/schema.js @@ -109,6 +109,15 @@ userSchema.index( userSchema.plugin(passportLocalMongoose); userSchema.plugin(findOrCreate); +// Never serialize the password hash/salt to API clients. +userSchema.set("toJSON", { + transform: (_doc, ret) => { + delete ret.salt; + delete ret.hash; + return ret; + }, +}); + //organizational unit const organizationalUnitSchema = new mongoose.Schema({ unit_id: { From cc304f5154fbe7946962bfd57cb5a9aea64e7593 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 18:49:21 +0530 Subject: [PATCH 02/31] fix(backend): start server without Google OAuth credentials configured (cherry picked from commit cc67e07d1a9c0ba37a74845679f15830a97d6476) --- backend/models/passportConfig.js | 94 +++++++++++++++++--------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/backend/models/passportConfig.js b/backend/models/passportConfig.js index 82cb533f..fccffb20 100644 --- a/backend/models/passportConfig.js +++ b/backend/models/passportConfig.js @@ -14,52 +14,58 @@ passport.use( ); // Google OAuth Strategy -passport.use( - new GoogleStrategy( - { - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: `${process.env.BACKEND_URL}/auth/google/verify`, // Update with your callback URL - }, - async (accessToken, refreshToken, profile, done) => { - // Check if the user already exists in your database - if (!isIITBhilaiEmail(profile.emails[0].value)) { - console.log("Google OAuth blocked for: ", profile.emails[0].value); - return done(null, false, { - message: "Only @iitbhilai.ac.in emails are allowed.", - }); - } - try { - const user = await User.findOne({ username: profile.emails[0].value }); - - if (user) { - // If user exists, return the user - return done(null, user); +if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) { + passport.use( + new GoogleStrategy( + { + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: `${process.env.BACKEND_URL}/auth/google/verify`, // Update with your callback URL + }, + async (accessToken, refreshToken, profile, done) => { + // Check if the user already exists in your database + if (!isIITBhilaiEmail(profile.emails[0].value)) { + console.log("Google OAuth blocked for: ", profile.emails[0].value); + return done(null, false, { + message: "Only @iitbhilai.ac.in emails are allowed.", + }); } - // If user doesn't exist, create a new user in your database - const newUser = new User({ - username: profile.emails[0].value, - role: "STUDENT", - strategy: "google", - personal_info: { - name: profile.displayName || "No Name", - email: profile.emails[0].value, - profilePic: - profile.photos && profile.photos.length > 0 - ? profile.photos[0].value - : "https://www.gravatar.com/avatar/?d=mp", - }, - onboardingComplete: false, - }); + try { + const user = await User.findOne({ username: profile.emails[0].value }); - await newUser.save(); - return done(null, newUser); - } catch (error) { - return done(error); - } - }, - ), -); + if (user) { + // If user exists, return the user + return done(null, user); + } + // If user doesn't exist, create a new user in your database + const newUser = new User({ + username: profile.emails[0].value, + role: "STUDENT", + strategy: "google", + personal_info: { + name: profile.displayName || "No Name", + email: profile.emails[0].value, + profilePic: + profile.photos && profile.photos.length > 0 + ? profile.photos[0].value + : "https://www.gravatar.com/avatar/?d=mp", + }, + onboardingComplete: false, + }); + + await newUser.save(); + return done(null, newUser); + } catch (error) { + return done(error); + } + }, + ), + ); +} else { + console.warn( + "Google OAuth strategy disabled: GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET not set.", + ); +} passport.serializeUser((user, done) => { done(null, user); From 704424fe5ea5932dda255c6fb6ff29216b9906f8 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 18:49:24 +0530 Subject: [PATCH 03/31] chore(backend): fix eslint config and clear lint errors - set parserOptions.ecmaVersion=2022 so optional chaining parses - enable jest globals and allow test-only devDependencies - declare node engines (>=18) - drop unused imports/vars flagged by no-unused-vars (cherry picked from commit 610fb29f30a992f28989ca6fc7de7e2a1b4f95ae) --- .../controllers/certificateBatchController.js | 5 ++--- backend/package.json | 20 ++++++++++++++++++- backend/routes/events.js | 2 -- backend/routes/onboarding.js | 1 - backend/routes/orgUnit.js | 11 ---------- backend/services/certificates.service.js | 1 + backend/utils/renderPdf.js | 2 -- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/backend/controllers/certificateBatchController.js b/backend/controllers/certificateBatchController.js index f6f7fab4..cfa3d5b4 100644 --- a/backend/controllers/certificateBatchController.js +++ b/backend/controllers/certificateBatchController.js @@ -9,7 +9,6 @@ const { findEvent } = require("../services/event.service"); const { findTemplate } = require("../services/template.service"); const { getApprovers } = require("../services/user.service"); const { - getOrganization, getCoordinatorOrganization, } = require("../services/organization.service"); const { HttpError } = require("../utils/httpError"); @@ -107,7 +106,7 @@ async function createBatch(req, res) { } - const newBatch = await CertificateBatch.create({ + await CertificateBatch.create({ title, eventId: event._id, templateId: template._id, @@ -271,7 +270,7 @@ async function duplicateBatch(req, res) { const array = batch.title.split("(Copy)"); const count = array.length -1; const title = `${array[0]} Copy(${count})`; - const newBatch = await CertificateBatch.create({ + await CertificateBatch.create({ ...batch.toObject(), title: title, lifecycleStatus: "Draft", diff --git a/backend/package.json b/backend/package.json index 115d9185..c8eb6f90 100644 --- a/backend/package.json +++ b/backend/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "description": "This is the backend for the cosa database GUI", "main": "index.js", + "engines": { + "node": ">=18" + }, "scripts": { "test": "jest --runInBand", "prepare": "node -e \"if(process.env.NODE_ENV !== 'production'){process.exit(1)}\" || (cd .. && husky install)", @@ -23,7 +26,22 @@ ], "env": { "node": true, - "es6": true + "es6": true, + "jest": true + }, + "parserOptions": { + "ecmaVersion": 2022 + }, + "rules": { + "node/no-unpublished-require": [ + "error", + { + "allowModules": [ + "supertest", + "mongodb-memory-server" + ] + } + ] } }, "author": "", diff --git a/backend/routes/events.js b/backend/routes/events.js index fdad2ade..77ffe442 100644 --- a/backend/routes/events.js +++ b/backend/routes/events.js @@ -1,7 +1,5 @@ const express = require("express"); const router = express.Router(); -const { Event, User, OrganizationalUnit } = require("../models/schema"); -const { v4: uuidv4 } = require("uuid"); const isAuthenticated = require("../middlewares/isAuthenticated"); const isEventContact = require("../middlewares/isEventContact"); const authorizeRole = require("../middlewares/authorizeRole"); diff --git a/backend/routes/onboarding.js b/backend/routes/onboarding.js index 73cd3687..1af4ca29 100644 --- a/backend/routes/onboarding.js +++ b/backend/routes/onboarding.js @@ -1,6 +1,5 @@ const express = require("express"); const router = express.Router(); -const { User } = require("../models/schema"); const isAuthenticated = require("../middlewares/isAuthenticated"); const onboardingController = require( diff --git a/backend/routes/orgUnit.js b/backend/routes/orgUnit.js index 263c18e1..7fff0465 100644 --- a/backend/routes/orgUnit.js +++ b/backend/routes/orgUnit.js @@ -1,17 +1,6 @@ // routes/club.js const express = require("express"); const router = express.Router(); -const mongoose = require("mongoose"); -const { v4: uuidv4 } = require("uuid"); -const { - OrganizationalUnit, - Event, - Position, - PositionHolder, - Achievement, - Feedback, - User, -} = require("../models/schema"); const isAuthenticated = require("../middlewares/isAuthenticated"); const authorizeRole = require("../middlewares/authorizeRole"); const { ROLE_GROUPS } = require("../utils/roles"); diff --git a/backend/services/certificates.service.js b/backend/services/certificates.service.js index 5a11bdd8..4d009109 100644 --- a/backend/services/certificates.service.js +++ b/backend/services/certificates.service.js @@ -65,6 +65,7 @@ async function generateCertificates(batch) { })), }; + // Generate PDF const pdfId = await renderToPdf(data, browser); diff --git a/backend/utils/renderPdf.js b/backend/utils/renderPdf.js index a4e37810..f78d2517 100644 --- a/backend/utils/renderPdf.js +++ b/backend/utils/renderPdf.js @@ -72,8 +72,6 @@ const watermarkLogoDataUri = loadAsDataUri( console.log("Certificate successfully generated at", outputPath); return fileId; - } catch (err) { - throw err; } finally { if (page) { try { From c7c37769df6c7bdae5b064e8e8a502a495a6ef35 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 18:51:41 +0530 Subject: [PATCH 04/31] fix(frontend): clear lint warnings so CI build passes - remove unused imports/vars flagged by no-unused-vars - fix exhaustive-deps warnings in useCallback/useEffect - StudentPanel: move participants/studentIds out of render, add useMemo - drop unused Field/inputStyle/InfoTile/labelColor helpers in Requests modals (cherry picked from commit a13d002b3372f9d97fb588fd5e57f5476ea60289) --- .../src/Components/Batches/StudentPanel.jsx | 36 +++++------- frontend/src/Components/Batches/batchCard.jsx | 2 - frontend/src/Components/Batches/batches.jsx | 10 ++-- .../src/Components/Batches/modalDialog.jsx | 10 +--- .../Certificates/CertificatesList.jsx | 10 ---- frontend/src/Components/Requests/Card.jsx | 1 - frontend/src/Components/Requests/Requests.jsx | 12 +--- .../src/Components/Requests/editModal.jsx | 57 +------------------ .../src/Components/Requests/viewModal.jsx | 46 +-------------- 9 files changed, 30 insertions(+), 154 deletions(-) diff --git a/frontend/src/Components/Batches/StudentPanel.jsx b/frontend/src/Components/Batches/StudentPanel.jsx index 6bd582e4..1fe6b9bd 100644 --- a/frontend/src/Components/Batches/StudentPanel.jsx +++ b/frontend/src/Components/Batches/StudentPanel.jsx @@ -1,10 +1,8 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { fetchBatchUsers } from "../../services/batch"; -import { Users, ChevronDown, ChevronUp, Search, X } from "lucide-react"; +import { Users, Search, X } from "lucide-react"; import { Avatar, Checkbox } from "../Batches/modalDialog"; import { Modal } from "./ui"; -/* ─── tiny helpers ─────────────────────────────────────────── */ -const toId = (s) => s?._id; /* ─── StudentsPanel ─────────────────────────────────────────── */ export default function StudentsPanel({ @@ -18,8 +16,8 @@ export default function StudentsPanel({ const [loading, setLoading] = useState(false); const [localSelected, setLocalSelected] = useState(new Set()); const [search, setSearch] = useState(""); - let studentIds = form.students || []; - let count = studentIds?.length || 0; + const studentIds = useMemo(() => form.students || [], [form.students]); + const count = studentIds?.length || 0; /* reset when panel closes */ const closePanel = () => { setOpen(false); @@ -29,29 +27,30 @@ export default function StudentsPanel({ /* fetch when opening */ useEffect(() => { if (!open || !selectedEvent) return; - if (count === 0) { - count = Object.keys(selectedEvent?.participants).length || 0; - if (count === 0) { - setDetails([]); - setLocalSelected(new Set()); - return; - } else studentIds = selectedEvent.participants || []; + let ids = studentIds; + if (ids.length === 0 && selectedEvent?.participants?.length) { + ids = selectedEvent.participants; + } + if (ids.length === 0) { + setDetails([]); + setLocalSelected(new Set()); + return; } let cancelled = false; setLoading(true); - fetchBatchUsers(studentIds).then((data) => { + fetchBatchUsers(ids).then((data) => { if (cancelled) return; setDetails(Array.isArray(data) ? data : []); - setLocalSelected(new Set(studentIds)); + setLocalSelected(new Set(ids)); setLoading(false); }); return () => { cancelled = true; }; - }, [open, selectedEvent]); + }, [open, selectedEvent, studentIds]); const toggle = (id) => setLocalSelected((prev) => { @@ -88,11 +87,6 @@ export default function StudentsPanel({ const allSelected = details.length > 0 && details.every((s) => localSelected.has(s?._id)); - const pendingChanges = - open && - !isViewOnly && - (localSelected.size !== count || - !studentIds.every((id) => localSelected.has(id))); return ( <> {/* ── trigger button ── */} diff --git a/frontend/src/Components/Batches/batchCard.jsx b/frontend/src/Components/Batches/batchCard.jsx index 8ef9acee..be6dea71 100644 --- a/frontend/src/Components/Batches/batchCard.jsx +++ b/frontend/src/Components/Batches/batchCard.jsx @@ -285,8 +285,6 @@ export function BatchList({ {/* Body */} {filtered?.map((b, i) => { - const c = BATCH_COLORS[b.color % BATCH_COLORS.length]; - return ( { const list = batches || []; @@ -316,7 +316,7 @@ export default function BatchesPage() { fire("Failed to " + action); } }, - [form], + [form, editing, closeModal, isUserLoggedIn?._id], ); const delBatch = useCallback(async (batch) => { @@ -324,14 +324,14 @@ export default function BatchesPage() { response && fire(response); const updated = await fetchBatches(isUserLoggedIn?._id); if (updated && updated.length !== 0) setBatches(updated); - }, []); + }, [isUserLoggedIn?._id]); const archiveBatch = useCallback(async (id) => { const response = await archiveBatchApi(id); response && fire(response); const updated = await fetchBatches(isUserLoggedIn?._id); if (updated && updated.length !== 0) setBatches(updated); - }, []); + }, [isUserLoggedIn?._id]); const dupBatch = useCallback(async (b) => { const response = await duplicateBatch(b?._id); @@ -339,7 +339,7 @@ export default function BatchesPage() { const updated = await fetchBatches(isUserLoggedIn?._id); if (updated && updated.length !== 0) setBatches(updated); - }, []); + }, [isUserLoggedIn?._id]); return (
diff --git a/frontend/src/Components/Batches/modalDialog.jsx b/frontend/src/Components/Batches/modalDialog.jsx index 188084e4..4f3c5cab 100644 --- a/frontend/src/Components/Batches/modalDialog.jsx +++ b/frontend/src/Components/Batches/modalDialog.jsx @@ -1,13 +1,9 @@ -import { useState, useEffect } from "react"; +import { useEffect } from "react"; import { Modal, Field, Divider } from "./ui"; -import { fetchBatchUsers } from "../../services/batch"; -import { Users, ChevronDown, ChevronUp, Search, X, Plus } from "lucide-react"; +import { Plus } from "lucide-react"; import StudentsPanel from "./StudentPanel"; /* ─── tiny helpers ─────────────────────────────────────────── */ -const toId = (s) => - s && typeof s === "object" ? (s._id || s).toString() : String(s); - const initials = (name = "") => name .split(" ") @@ -135,7 +131,7 @@ export default function ModalDialog({ signatoryDetails: [{ name: "", role: "" }], })); } - }, []); + }, [form.signatoryDetails.length, setForm]); const selectStyle = { width: "100%", diff --git a/frontend/src/Components/Certificates/CertificatesList.jsx b/frontend/src/Components/Certificates/CertificatesList.jsx index 182218da..df14a98f 100644 --- a/frontend/src/Components/Certificates/CertificatesList.jsx +++ b/frontend/src/Components/Certificates/CertificatesList.jsx @@ -83,16 +83,6 @@ const CertificatesList = () => { }); }; - const isSafeCertificateUrl = (url) => { - try { - const parsed = new URL(url, window.location.origin); - return parsed.protocol === "http:" || parsed.protocol === "https:"; - } catch { - return false; - } - }; - - const filterButtons = [ { label: "ALL", value: "ALL" }, { label: "Pending", value: "Pending" }, diff --git a/frontend/src/Components/Requests/Card.jsx b/frontend/src/Components/Requests/Card.jsx index a1b140d1..76867c7a 100644 --- a/frontend/src/Components/Requests/Card.jsx +++ b/frontend/src/Components/Requests/Card.jsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { useAdminContext } from "../../context/AdminContext"; const STATUS = { diff --git a/frontend/src/Components/Requests/Requests.jsx b/frontend/src/Components/Requests/Requests.jsx index b025b605..8a3a4ee7 100644 --- a/frontend/src/Components/Requests/Requests.jsx +++ b/frontend/src/Components/Requests/Requests.jsx @@ -1,4 +1,4 @@ -import { Search, OctagonAlert, FileText } from "lucide-react"; +import { Search, FileText } from "lucide-react"; import { useEffect, useState, useCallback } from "react"; import { useRequest } from "../../context/RequestContext"; import { useAdminContext } from "../../context/AdminContext"; @@ -56,7 +56,7 @@ export default function Requests() { } fetchData(); - }, [isUserLoggedIn]); + }, [isUserLoggedIn, navigate]); const filteredRequests = useCallback(() => { return (requests || []).filter((req) => { @@ -103,7 +103,7 @@ export default function Requests() { rejected, total, }); - }, [requests]); + }, [requests, setRequestStatus]); const approve = async function (batch) { const response = await approveBatch(batch); @@ -120,12 +120,6 @@ export default function Requests() { updated && setRequests(updated); }; - function handleUpdateRequest(updatedRequest) { - setRequests((prev) => - prev.map((req) => (req.id === updatedRequest.id ? updatedRequest : req)), - ); - } - if (loading) { return (
diff --git a/frontend/src/Components/Requests/editModal.jsx b/frontend/src/Components/Requests/editModal.jsx index 4bcf1950..26e5d0fb 100644 --- a/frontend/src/Components/Requests/editModal.jsx +++ b/frontend/src/Components/Requests/editModal.jsx @@ -1,16 +1,5 @@ import { useState } from "react"; -import { - X, - CalendarDays, - Users, - UserCircle2, - Award, - Pencil, - FlameKindling, - Save, - Building2, - ChevronDown, -} from "lucide-react"; +import { X, Pencil, Save } from "lucide-react"; import { Overlay, C } from "./ui"; import { approverEditBatch, fetchBatches } from "../../services/batch"; @@ -18,51 +7,9 @@ import { toast } from "react-toastify"; import { useAdminContext } from "../../context/AdminContext"; /* EDIT MODAL */ -const Field = ({ label, icon: Icon, children, half }) => ( -
- - {children} -
-); - -const inputStyle = { - width: "100%", - padding: "10px 14px", - borderRadius: 10, - fontSize: 14, - color: C.text, - background: C.white, - border: `1.5px solid ${C.border}`, - outline: "none", - boxSizing: "border-box", - fontFamily: "inherit", - transition: "border-color 0.15s", -}; - export const EditModal = ({ request, onClose, setRequests }) => { const { isUserLoggedIn } = useAdminContext(); - const [form, setForm] = useState({ ...request }); + const [form] = useState({ ...request }); const [selected, setSelected] = useState(new Set()); const allIds = (request.users || []).map((u) => u._id); diff --git a/frontend/src/Components/Requests/viewModal.jsx b/frontend/src/Components/Requests/viewModal.jsx index 1ff2d312..cc4b0ef2 100644 --- a/frontend/src/Components/Requests/viewModal.jsx +++ b/frontend/src/Components/Requests/viewModal.jsx @@ -1,47 +1,5 @@ -import { Overlay, Pill, C } from "./ui"; -import { X, CalendarDays, Users, UserCircle2, Award } from "lucide-react"; - -const labelColor = { - Approved: "green", - Rejected: "red", - Pending: "amber", -}; - -/* ─── info tile (used in View modal) ────────────────────── */ -const InfoTile = ({ icon: Icon, label, value, wide }) => ( -
-
- - - {label} - -
- - - {value} - -
-); +import { Overlay, C } from "./ui"; +import { X } from "lucide-react"; /* VIEW MODAL */ export const ViewModal = ({ From db5b1bca6106c7fe2a9a62d7eb7fca1a54db9b44 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 18:51:45 +0530 Subject: [PATCH 05/31] test(frontend): replace stale CRA boilerplate test with app smoke test - mock axios via module factory so renders in jsdom - assert unauthenticated users land on the Login page - userIcon: guard undefined academic info instead of printing 'undefined' (cherry picked from commit f3cc2d6236f3420eb90da83281a2639aa67fdc71) --- frontend/src/App.test.js | 26 ++++++++++++++++----- frontend/src/Components/common/userIcon.jsx | 2 ++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.test.js b/frontend/src/App.test.js index 1f03afee..a8cf4236 100644 --- a/frontend/src/App.test.js +++ b/frontend/src/App.test.js @@ -1,8 +1,22 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; +import { render, screen, waitFor } from "@testing-library/react"; +import App from "./App"; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); +jest.mock("axios", () => { + const instance = { + get: jest.fn(() => Promise.resolve({ data: null })), + post: jest.fn(() => Promise.resolve({ data: null })), + put: jest.fn(() => Promise.resolve({ data: null })), + patch: jest.fn(() => Promise.resolve({ data: null })), + delete: jest.fn(() => Promise.resolve({ data: null })), + }; + return { create: jest.fn(() => instance) }; }); + +test("renders the app and lands on the login page for unauthenticated users", async () => { + render(); + + // Wait for the auth check (fetchCredentials -> null) to complete. + await waitFor(() => { + expect(screen.getByRole("heading", { name: "Login" })).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/frontend/src/Components/common/userIcon.jsx b/frontend/src/Components/common/userIcon.jsx index 551a3b93..dea342f3 100644 --- a/frontend/src/Components/common/userIcon.jsx +++ b/frontend/src/Components/common/userIcon.jsx @@ -30,12 +30,14 @@ const UserIcon = () => { let details = ""; if (profile.academic_info) { + const parts = [ profile.academic_info.batch_year, profile.academic_info.program, profile.user_id, ].filter(Boolean); details = parts.length > 0 ? parts.join(" | ") : ""; + } else { details = profile.personal_info?.email || "No academic info available"; } From 4a947a510288b1f3a5795c1e0e825a01693334ce Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 18:51:45 +0530 Subject: [PATCH 06/31] docs: standardize backend port on 8000 in README and env example (cherry picked from commit dff0a8ef6986136526c9173b0e1f97529a3d9927) --- README.md | 2 +- backend/.env.example | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4ca18f7..59df290b 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ cd Student_Database_COSA ```bash node index.js ``` -The backend server should now be running on `http://localhost:5000`.Keep this terminal open. +The backend server should now be running on `http://localhost:8000`.Keep this terminal open. ### 3. Frontend Setup **Open a new, separate terminal window.** This is important, as your backend server needs to keep running in the first terminal. diff --git a/backend/.env.example b/backend/.env.example index d0a82c30..61adbf09 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,8 +1,8 @@ MONGODB_URI = mongodb://localhost:27017/cosadatabase JWT_SECRET_TOKEN='secret-token' FRONTEND_URL=http://localhost:3000 -BACKEND_URL=http://localhost:5000 -PORT=5000 +BACKEND_URL=http://localhost:8000 +PORT=8000 GOOGLE_CLIENT_ID=OAuth_Client_ID_from_google_cloud_console GOOGLE_CLIENT_SECRET=OAuth_Client_Secret_from_google_cloud_console From 1041ebdbd52ef855c9fc8e5f98911a449ecd7975 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 19:39:11 +0530 Subject: [PATCH 07/31] feat(docker): inject REACT_APP_BACKEND_URL as a build arg for the frontend CRA inlines REACT_APP_* vars at build time, so the backend URL must be available during the nginx image build, not just at runtime. (cherry picked from commit 56b58e61f4a03716430ab59896b3aa51ce25806e) --- docker-compose.yml | 3 +++ frontend/Dockerfile | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a6d8d089..f0ecfdfb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,9 @@ services: frontend: build: context: ./frontend + args: + # Backend URL as reachable from the user's browser (host port). + REACT_APP_BACKEND_URL: http://localhost:8000 container_name: frontend ports: - "3000:80" # Map host port 3000 to container port 80 (nginx default) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f1ed48c5..5356a5de 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -7,6 +7,10 @@ RUN npm install --legacy-peer-deps COPY . . +# Injected at build time: CRA inlines REACT_APP_* into the bundle. +ARG REACT_APP_BACKEND_URL +ENV REACT_APP_BACKEND_URL=$REACT_APP_BACKEND_URL + RUN npm run build FROM nginx:stable-alpine From a5bf9bf996f9d42124e43f2fd2addcfcd7e075fa Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 19:39:16 +0530 Subject: [PATCH 08/31] fix(frontend): point PresidentDashboard at real backend endpoints The dashboard called dead routes (/room/requests, /events, /tenure, /api/activities/recent) via bare fetch. Use the shared authenticated axios instance against /dashboard/stats, /rooms/bookings, /events/latest. (cherry picked from commit 0eb8197db5bd80740fe966cc6a0ac5e66a79fd55) --- .../President/PresidentDashboard.jsx | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/frontend/src/Components/President/PresidentDashboard.jsx b/frontend/src/Components/President/PresidentDashboard.jsx index 117500d8..ffaf767f 100644 --- a/frontend/src/Components/President/PresidentDashboard.jsx +++ b/frontend/src/Components/President/PresidentDashboard.jsx @@ -1,8 +1,7 @@ import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import { navItems } from "../../config/presidentConfig.js"; -const API_BASE_URL = - process.env.REACT_APP_BACKEND_URL || "http://localhost:8000"; +import api from "../../utils/api"; const PresidentDashboard = () => { // States for various dashboard metrics @@ -19,32 +18,33 @@ const PresidentDashboard = () => { try { setIsLoading(true); - // Implement actual API calls for each metric - // Example: - const requestsResponse = await fetch( - `${API_BASE_URL}/room/requests?status=pending`, - ); - const requestsData = await requestsResponse.json(); - setPendingRequests(requestsData.length); - - const bookingsResponse = await fetch(`${API_BASE_URL}/room/requests`); - const bookingsData = await bookingsResponse.json(); - setRoomBookings(bookingsData.length); + const [statsRes, bookingsRes, eventsRes] = await Promise.all([ + api.get("/dashboard/stats"), + api.get("/rooms/bookings"), + api.get("/events/latest"), + ]); - const eventsResponse = await fetch(`${API_BASE_URL}/events`); - const eventsData = await eventsResponse.json(); - setUpcomingEvents(eventsData.length); + const stats = statsRes.data || {}; + const bookings = Array.isArray(bookingsRes.data) + ? bookingsRes.data + : []; + const latestEvents = Array.isArray(eventsRes.data) + ? eventsRes.data + : []; - const cosaResponse = await fetch(`${API_BASE_URL}/tenure`); - const cosaData = await cosaResponse.json(); - setCosaRecords(cosaData.length); - - const activitiesResponse = await fetch("/api/activities/recent"); - const activitiesData = await activitiesResponse.json(); - setRecentActivities(activitiesData); + setPendingRequests(stats.pendingRoomRequests || 0); + setRoomBookings(bookings.length); + setUpcomingEvents(latestEvents.length); + setCosaRecords(stats.totalOrgUnits || 0); + setRecentActivities( + latestEvents.map((event) => ({ + type: "event", + description: event.title, + timestamp: event.date, + })), + ); } catch (error) { console.error("Error fetching dashboard data:", error); - // Implement error handling here } finally { setIsLoading(false); } From 68f165c92314d289b892d6bb24ed1bc836b7e58f Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 19:39:20 +0530 Subject: [PATCH 09/31] fix(frontend): provide SidebarProvider for direct /certificates route CertificatesPage uses useSidebar(), which throws outside a provider. Only the /dashboard layout supplied one, so deep-linking to /certificates crashed. Mirror Dashboard.jsx and wrap the route. (cherry picked from commit 7f7567261c419bf932ac65b4a2d4aa09c46380c6) --- frontend/src/routes/StudentRoutes.js | 37 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/frontend/src/routes/StudentRoutes.js b/frontend/src/routes/StudentRoutes.js index e28f0c9f..6ac314c1 100644 --- a/frontend/src/routes/StudentRoutes.js +++ b/frontend/src/routes/StudentRoutes.js @@ -12,9 +12,12 @@ import Home from "../Components/OldComponents/Home"; import StudentProfile from "../Components/Profile/ProfilePage"; import TenurePage from "../pages/TenurePage"; import CertificatesPage from "../pages/certificatesPage"; +import { SidebarProvider } from "../hooks/useSidebar"; +import { NavbarConfig } from "../config/navbarConfig"; -export const getStudentRoutes = (isUserLoggedIn, isOnboardingComplete) => [ +export const getStudentRoutes = (isUserLoggedIn, isOnboardingComplete) => { + return [ [ } />, - - - - } -/>, -]; + + + + + + } + />, + ]; +}; From c2eb330a696664d6969de2e09c2f82c36d15e668 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 19:39:25 +0530 Subject: [PATCH 10/31] chore(frontend): remove incomplete flat eslint config eslint v8 auto-loads eslint.config.mjs whenever present, silently replacing the react-app config used by the build. The flat config only enabled two react rules (no no-unused-vars, no react-hooks), so lint was weaker than CI. Removing it lets eslint fall back to the package.json eslintConfig (react-app). (cherry picked from commit 9f5919ca1aea5055e8efacc7d42ac54145f956e1) --- frontend/eslint.config.mjs | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 frontend/eslint.config.mjs diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs deleted file mode 100644 index ccb2ad5e..00000000 --- a/frontend/eslint.config.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import react from 'eslint-plugin-react'; -import pkg from 'globals'; -const { browser } = pkg; - -export default [ - { - files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], - plugins: { - react, - }, - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...browser, - }, - }, - rules: { - // ... any rules you want - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', - }, - // ... others are omitted for brevity - }, -]; From ad127054b21f88045facf84efde74196b1aea3e2 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Fri, 14 Aug 2026 20:10:57 +0530 Subject: [PATCH 11/31] fix(frontend): add missing /api prefix to PresidentDashboard requests The backend mounts dashboard/rooms/events under /api/*, so the three calls returned 404 and every stat stayed at zero. (cherry picked from commit 6bd7a31821780607ec55bb273d93d0d7165bd693) --- frontend/src/Components/President/PresidentDashboard.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/Components/President/PresidentDashboard.jsx b/frontend/src/Components/President/PresidentDashboard.jsx index ffaf767f..fbc149bf 100644 --- a/frontend/src/Components/President/PresidentDashboard.jsx +++ b/frontend/src/Components/President/PresidentDashboard.jsx @@ -19,9 +19,9 @@ const PresidentDashboard = () => { setIsLoading(true); const [statsRes, bookingsRes, eventsRes] = await Promise.all([ - api.get("/dashboard/stats"), - api.get("/rooms/bookings"), - api.get("/events/latest"), + api.get("/api/dashboard/stats"), + api.get("/api/rooms/bookings"), + api.get("/api/events/latest"), ]); const stats = statsRes.data || {}; From 6a57a4ce6f0815015d7c1f0f85c00a04e6a8a86d Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:09:49 +0530 Subject: [PATCH 12/31] fix(backend): validate JWT_SECRET_TOKEN at boot (cherry picked from commit a3cea8cff4a3bcaf7697f98dd31e10337571b018) --- backend/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/index.js b/backend/index.js index abe27462..26b64c17 100644 --- a/backend/index.js +++ b/backend/index.js @@ -47,6 +47,10 @@ if (!process.env.SESSION_SECRET) { throw new Error("SESSION_SECRET environment variable is required"); } +if (!process.env.JWT_SECRET_TOKEN) { + throw new Error("JWT_SECRET_TOKEN environment variable is required"); +} + app.use( session({ secret: process.env.SESSION_SECRET, From 0627ea1a6f069a7bdf09825a6704127c12b61ba4 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:09:49 +0530 Subject: [PATCH 13/31] fix(backend): bind profile photo and update to authenticated user (cherry picked from commit b617456f23658cc8f0daaa59b99243750a48da27) --- backend/controllers/profileController.js | 25 +++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/backend/controllers/profileController.js b/backend/controllers/profileController.js index 2fe38e0e..2217e5d9 100644 --- a/backend/controllers/profileController.js +++ b/backend/controllers/profileController.js @@ -11,12 +11,7 @@ cloudinary.config({ exports.updateProfilePhoto = async (req, res) => { try { - const { ID_No } = req.body; - if (!ID_No) { - return res.status(400).json({ error: "ID_No is required" }); - } - - const user = await User.findOne({ user_id: ID_No }); + const user = await User.findById(req.user.id); if (!user) { return res.status(404).json({ error: "User not found" }); } @@ -69,12 +64,7 @@ exports.updateProfilePhoto = async (req, res) => { // Delete profile photo (reset to default) exports.deleteProfilePhoto = async (req, res) => { try { - const { ID_No } = req.query; // Get ID_No from frontend for DELETE - if (!ID_No) { - return res.status(400).json({ error: "ID_No is required" }); - } - - const user = await User.findOne({ user_id: ID_No }); // Capital User + const user = await User.findById(req.user.id); if (!user) { return res.status(404).json({ error: "User not found" }); } @@ -116,6 +106,13 @@ exports.updateStudentProfile = async (req, res) => { .json({ success: false, message: "Student not found" }); } + if (user._id.toString() !== req.user.id.toString()) { + return res.status(403).json({ + success: false, + message: "Not authorized to update this profile", + }); + } + // ---------- PERSONAL INFO ---------- if (updatedDetails.personal_info) { const { @@ -125,7 +122,6 @@ exports.updateStudentProfile = async (req, res) => { gender, date_of_birth, profilePic, - cloudinaryUrl, } = updatedDetails.personal_info; if (name) { @@ -146,9 +142,6 @@ exports.updateStudentProfile = async (req, res) => { if (profilePic) { user.personal_info.profilePic = profilePic; } - if (cloudinaryUrl) { - user.personal_info.cloudinaryUrl = cloudinaryUrl; - } } // ---------- ACADEMIC INFO ---------- From 0b649fd4a6255ab850f044911ffa74aed1724bc1 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:09:49 +0530 Subject: [PATCH 14/31] fix(backend): bind achievement and user-skill writes to authenticated user (cherry picked from commit 95eea26fc5e28fa4bde561b6ad4fcd9a359092e0) --- backend/controllers/achievementController.js | 8 ++++++-- backend/controllers/skillController.js | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/controllers/achievementController.js b/backend/controllers/achievementController.js index b46ff2e1..0478b88d 100644 --- a/backend/controllers/achievementController.js +++ b/backend/controllers/achievementController.js @@ -1,5 +1,6 @@ const { Achievement } = require("../models/schema"); const { v4: uuidv4 } = require("uuid"); +const { ROLE_GROUPS } = require("../utils/roles"); // GET unverified achievements by type const getUnendorsedAchievements = async (req, res) => { @@ -94,7 +95,10 @@ const addAchievement = async (req, res) => { user_id, } = req.body; - if (!title || !category || !date_achieved || !user_id) { + const isAdmin = ROLE_GROUPS.ADMIN.includes(req.user.role); + const targetUserId = isAdmin ? user_id : req.user._id; + + if (!title || !category || !date_achieved || !targetUserId) { return res.status(400).json({ message: "Missing required fields", }); @@ -102,7 +106,7 @@ const addAchievement = async (req, res) => { const achievement = new Achievement({ achievement_id: uuidv4(), - user_id, + user_id: targetUserId, title, description, category, diff --git a/backend/controllers/skillController.js b/backend/controllers/skillController.js index 7a0ef511..3604251e 100644 --- a/backend/controllers/skillController.js +++ b/backend/controllers/skillController.js @@ -1,5 +1,6 @@ const { UserSkill, Skill } = require("../models/schema"); const { v4: uuidv4 } = require("uuid"); +const { ROLE_GROUPS } = require("../utils/roles"); // GET unendorsed user skills for a particular skill type exports.getUnendorsedUserSkills = async (req, res) => { @@ -176,8 +177,11 @@ exports.createUserSkill = async (req, res) => { try { const { user_id, skill_id, proficiency_level, position_id } = req.body; + const isAdmin = ROLE_GROUPS.ADMIN.includes(req.user.role); + const targetUserId = isAdmin ? user_id : req.user._id; + const newUserSkill = new UserSkill({ - user_id, + user_id: targetUserId, skill_id, proficiency_level, position_id: position_id || null, From ecf22cd111857fe1355687a99210700ae1a5a980 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:09:52 +0530 Subject: [PATCH 15/31] fix(backend): gate feedback view to admins and bind feedback author (cherry picked from commit f31a9b6081b06517c8731da22c1877d9bd672a08) --- backend/controllers/feedbackController.js | 10 +++++++--- backend/routes/feedbackRoutes.js | 2 +- frontend/src/routes/PublicRoutes.js | 2 -- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/controllers/feedbackController.js b/backend/controllers/feedbackController.js index c93cd8cc..4171edeb 100644 --- a/backend/controllers/feedbackController.js +++ b/backend/controllers/feedbackController.js @@ -15,13 +15,12 @@ exports.addFeedback = async (req, res) => { type, target_type, target_id, - feedback_by, rating, comments, is_anonymous, } = req.body; - if (!type || !target_type || !target_id || !feedback_by) { + if (!type || !target_type || !target_id) { return res.status(400).json({ message: "Missing required fields", }); @@ -47,7 +46,7 @@ exports.addFeedback = async (req, res) => { type, target_type, target_id, - feedback_by, + feedback_by: req.user._id, rating, comments, is_anonymous: @@ -225,6 +224,11 @@ exports.viewFeedback = async (req, res) => { } const fbObj = fb.toObject(); + + if (fbObj.is_anonymous) { + fbObj.feedback_by = null; + } + fbObj.target_data = targetData; return fbObj; diff --git a/backend/routes/feedbackRoutes.js b/backend/routes/feedbackRoutes.js index 00338d78..7d049166 100644 --- a/backend/routes/feedbackRoutes.js +++ b/backend/routes/feedbackRoutes.js @@ -12,7 +12,7 @@ router.post("/add",isAuthenticated, feedbackController.addFeedback); router.get("/get-targetid",isAuthenticated, feedbackController.getTargetIds); -router.get("/view-feedback", feedbackController.viewFeedback); +router.get("/view-feedback",isAuthenticated,authorizeRole(ROLE_GROUPS.ADMIN), feedbackController.viewFeedback); // requires user middleware that attaches user info to req.user router.put("/mark-resolved/:id",isAuthenticated,authorizeRole(ROLE_GROUPS.ADMIN), feedbackController.markResolved); diff --git a/frontend/src/routes/PublicRoutes.js b/frontend/src/routes/PublicRoutes.js index 69576c50..cc64487e 100644 --- a/frontend/src/routes/PublicRoutes.js +++ b/frontend/src/routes/PublicRoutes.js @@ -3,7 +3,6 @@ import { Route } from "react-router-dom"; import { PublicRoute } from "../Components/common/ProtectedRoute"; import EventList from "../Components/Events/EventList"; import EventDetail from "../Components/Events/EventDetail"; -import ViewFeedback from "../Components/Feedback/ViewFeedback"; import Login from "../Components/Auth/Login"; import Register from "../Components/Auth/Register"; import ForgotPassword from "../Components/Auth/Forgot-Password/ForgotPassword"; @@ -13,7 +12,6 @@ export const getPublicRoutes = (isUserLoggedIn) => [ // Public routes accessible to everyone } />, } />, - } />, // Authentication routes - only for non-authenticated users Date: Sat, 15 Aug 2026 10:09:52 +0530 Subject: [PATCH 16/31] fix(backend): role-gate position and position-holder creation (cherry picked from commit b0aca0a8730c29c4696341833b82ad765fda154c) --- backend/routes/positionRoutes.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/routes/positionRoutes.js b/backend/routes/positionRoutes.js index 86059919..78ff60d9 100644 --- a/backend/routes/positionRoutes.js +++ b/backend/routes/positionRoutes.js @@ -1,13 +1,20 @@ const express = require("express"); const router = express.Router(); const isAuthenticated = require("../middlewares/isAuthenticated"); +const authorizeRole = require("../middlewares/authorizeRole"); +const { ROLE_GROUPS } = require("../utils/roles"); const positionController = require( "../controllers/positionController" ); -// POST for adding a new position -router.post("/add-position", isAuthenticated, positionController.addPosition); +// POST for adding a new position (admin roles only) +router.post( + "/add-position", + isAuthenticated, + authorizeRole(ROLE_GROUPS.ADMIN), + positionController.addPosition +); // for getting all the position router.get("/get-all", isAuthenticated, positionController.getAllPositions); @@ -16,6 +23,7 @@ router.get("/get-all", isAuthenticated, positionController.getAllPositions); router.post( "/add-position-holder", isAuthenticated, + authorizeRole(ROLE_GROUPS.ADMIN), positionController.addPositionHolder ); From c490aa08653a8c3ec93b8bb087936486efbfc77c Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:09:52 +0530 Subject: [PATCH 17/31] fix(backend): restrict event update fields and derive role from session (cherry picked from commit 64c231981dd461153dc3965f3015dd314e977335) --- backend/controllers/eventControllers.js | 30 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/backend/controllers/eventControllers.js b/backend/controllers/eventControllers.js index bf2edf17..22ff146d 100644 --- a/backend/controllers/eventControllers.js +++ b/backend/controllers/eventControllers.js @@ -215,7 +215,30 @@ exports.deleteEvent = async (req, res) => { exports.updateEvent = async (req, res) => { try { const { eventId } = req.params; - const updates = req.body; + + const allowedFields = [ + "title", + "description", + "category", + "type", + "schedule", + "registration", + "budget", + "status", + ]; + + const updates = {}; + for (const field of allowedFields) { + if (req.body[field] !== undefined) { + updates[field] = req.body[field]; + } + } + + if (Object.keys(updates).length === 0) { + return res.status(400).json({ + message: "No editable fields provided", + }); + } const event = await Event.findByIdAndUpdate( eventId, @@ -241,7 +264,6 @@ exports.updateEvent = async (req, res) => { return res.status(500).json({ message: "Server error", - error: err.message, }); } }; @@ -450,7 +472,7 @@ exports.registerForEvent = async (req, res) => { exports.getEventsByRole = async (req, res) => { - const userRole = req.params.userRole; + const userRole = req.user.role; try { let query = {}; @@ -465,7 +487,7 @@ exports.getEventsByRole = async (req, res) => { break; case "CLUB_COORDINATOR": { - const username = req.query.username; + const username = req.user.username; if (!username) { return res.status(400).json({ From ebfad24fafb4d1420747a70a289eee98fb5a9855 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:09:52 +0530 Subject: [PATCH 18/31] fix(backend): restrict batch edits to the batch initiator (cherry picked from commit c8f07ee0f40afa3503cc523476cf056ffd709632) --- backend/controllers/certificateBatchController.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/controllers/certificateBatchController.js b/backend/controllers/certificateBatchController.js index cfa3d5b4..4f3c6fae 100644 --- a/backend/controllers/certificateBatchController.js +++ b/backend/controllers/certificateBatchController.js @@ -187,6 +187,12 @@ async function editBatch(req, res) { return res.status(404).json({ message: "Batch not found" }); } + if (batch.initiatedBy.toString() !== id) { + return res.status(403).json({ + message: "You are not authorized to edit this batch", + }); + } + Object.assign(batch, validation.data); batch.lifecycleStatus = action; From 590658e0175d4cdcad4c641cb9134429d9e3d527 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:15:03 +0530 Subject: [PATCH 19/31] fix(frontend): render public events for guests and wrap in SidebarProvider (cherry picked from commit 8fe85ad7f551ebf56cba941e0d6f7808e540e856) --- frontend/src/Components/Events/EventList.jsx | 4 ++-- frontend/src/hooks/useEvents.js | 15 ++++++++++----- frontend/src/routes/PublicRoutes.js | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/frontend/src/Components/Events/EventList.jsx b/frontend/src/Components/Events/EventList.jsx index 667a64cc..c0b083bf 100644 --- a/frontend/src/Components/Events/EventList.jsx +++ b/frontend/src/Components/Events/EventList.jsx @@ -43,7 +43,7 @@ const EmptyState = () => { const EventList = () => { const { isUserLoggedIn } = useContext(AdminContext); const username = isUserLoggedIn?.username || ""; - const userRole = isUserLoggedIn?.role || "STUDENT"; + const userRole = isUserLoggedIn?.role || "GUEST"; const currentUserId = isUserLoggedIn?._id; const { events, loading, error, updateEvent } = useEvents(userRole, username); @@ -112,7 +112,7 @@ const EventList = () => { {events.length === 0 ? ( - ) : userRole === "STUDENT" ? ( + ) : userRole === "STUDENT" || userRole === "GUEST" ? (
{events.map((event, i) => ( { setLoading(true); setError(null); - let url = `/api/events/by-role/${userRole}`; - if (userRole === "CLUB_COORDINATOR" && username) { - url += `?username=${encodeURIComponent(username)}`; - } else if (userRole === "CLUB_COORDINATOR" && !username) { - throw new Error("Username is missing for Club Coordinator."); + let url; + if (userRole === "GUEST") { + url = `/api/events/events`; + } else { + url = `/api/events/by-role/${userRole}`; + if (userRole === "CLUB_COORDINATOR" && username) { + url += `?username=${encodeURIComponent(username)}`; + } else if (userRole === "CLUB_COORDINATOR" && !username) { + throw new Error("Username is missing for Club Coordinator."); + } } const response = await api.get(url); diff --git a/frontend/src/routes/PublicRoutes.js b/frontend/src/routes/PublicRoutes.js index cc64487e..3613c586 100644 --- a/frontend/src/routes/PublicRoutes.js +++ b/frontend/src/routes/PublicRoutes.js @@ -3,6 +3,8 @@ import { Route } from "react-router-dom"; import { PublicRoute } from "../Components/common/ProtectedRoute"; import EventList from "../Components/Events/EventList"; import EventDetail from "../Components/Events/EventDetail"; +import { SidebarProvider } from "../hooks/useSidebar"; +import { NavbarConfig } from "../config/navbarConfig"; import Login from "../Components/Auth/Login"; import Register from "../Components/Auth/Register"; import ForgotPassword from "../Components/Auth/Forgot-Password/ForgotPassword"; @@ -10,7 +12,18 @@ import ResetPassword from "../Components/Auth/Forgot-Password/ResetPassword"; export const getPublicRoutes = (isUserLoggedIn) => [ // Public routes accessible to everyone - } />, + + + + } + />, } />, // Authentication routes - only for non-authenticated users From b6aea0f23ff35a37e94e2d50e3a0389ec0fc609c Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:15:03 +0530 Subject: [PATCH 20/31] ci: pass SESSION_SECRET to backend, gate on health curl, fix EXPOSE (cherry picked from commit 54105d17ea83a2bce54c6cf9cd9ca4be6bf3fc9f) --- .github/workflows/deploy.yml | 3 +++ backend/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3c508cc9..d397f3e5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -53,6 +53,7 @@ jobs: docker run -d \ --name backend_test \ -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ + -e SESSION_SECRET=${{ secrets.SESSION_SECRET }} \ -e JWT_SECRET_TOKEN=${{ secrets.JWT_SECRET_TOKEN }} \ -e FRONTEND_URL=${{ secrets.FRONTEND_URL }} \ -e BACKEND_URL=${{ secrets.BACKEND_URL }} \ @@ -62,5 +63,7 @@ jobs: sleep 15 echo "Checking backend container logs:" docker logs backend_test + curl --fail http://localhost:8000/ || (echo "Backend container failed to respond!" && docker logs backend_test && exit 1) + docker stop backend_test - name: Push Backend Docker image run: docker push ${{ secrets.DOCKER_USERNAME }}/backend:latest diff --git a/backend/Dockerfile b/backend/Dockerfile index b8434627..0ad48cf8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -25,7 +25,7 @@ RUN addgroup -S appgroup && adduser -S appuser -G appgroup && chown -R appuser:a USER appuser -EXPOSE 3000 +EXPOSE 8000 CMD ["node", "index.js"] From 932247760a21fe3ca5c59bb2740b3572c42ebf99 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:15:03 +0530 Subject: [PATCH 21/31] ci: add root package.json with test/lint/build scripts and fix backend build (cherry picked from commit e15e0d9e4b77753437db11c18070ef2bfb2bbe78) --- README.md | 2 +- backend/package.json | 3 ++- frontend/package.json | 1 + package.json | 13 +++++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 package.json diff --git a/README.md b/README.md index 59df290b..0aa2b385 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ cd Student_Database_COSA # For Windows copy .env.example .env ``` - Now, open the new `.env` file and fill in your actual values. + Now, open the new `.env` file and fill in your actual values. `SESSION_SECRET` and `JWT_SECRET_TOKEN` are required — the server refuses to start without them. - **Seed the database:** This next command populates the database with initial necessary data. **You only need to run this once during the initial setup.** diff --git a/backend/package.json b/backend/package.json index c8eb6f90..38e661ec 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,10 +8,11 @@ }, "scripts": { "test": "jest --runInBand", + "lint": "eslint .", "prepare": "node -e \"if(process.env.NODE_ENV !== 'production'){process.exit(1)}\" || (cd .. && husky install)", "start": "node index.js", "dev": "nodemon index.js", - "build": "nodemon build.js" + "build": "node --check index.js" }, "lint-staged": { "*.js": [ diff --git a/frontend/package.json b/frontend/package.json index d7728bf7..0de56985 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", + "lint": "eslint src", "eject": "react-scripts eject" }, "lint-staged": { diff --git a/package.json b/package.json new file mode 100644 index 00000000..577869b9 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "student-database-cosa", + "version": "1.0.0", + "private": true, + "scripts": { + "pretest": "npm --prefix backend install --no-audit --no-fund && npm --prefix frontend install --no-audit --no-fund", + "prelint": "npm --prefix backend install --no-audit --no-fund && npm --prefix frontend install --no-audit --no-fund", + "prebuild": "npm --prefix backend install --no-audit --no-fund && npm --prefix frontend install --no-audit --no-fund", + "test": "npm --prefix backend test && CI=true npm --prefix frontend test", + "lint": "npm --prefix backend run lint && npm --prefix frontend run lint", + "build": "npm --prefix backend run build && npm --prefix frontend run build" + } +} \ No newline at end of file From 17d3ae09c6355df06a268dfec1b9566954e7207c Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:27:53 +0530 Subject: [PATCH 22/31] fix(backend): authenticate before authorizing club-coordinator analytics (cherry picked from commit e1ac75184a6de7cad5b08c35dc09a0170943ae04) --- backend/routes/analytics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/routes/analytics.js b/backend/routes/analytics.js index bf31fc49..81b4af1d 100644 --- a/backend/routes/analytics.js +++ b/backend/routes/analytics.js @@ -12,7 +12,7 @@ router.get('/president', isAuthenticated, authorizeRole(['PRESIDENT']), controll router.get('/gensec', isAuthenticated,authorizeRole([...ROLE_GROUPS.GENSECS]), controller.getGensecAnalytics); // Route to get analytics for club coordinators -router.get('/club-coordinator',authorizeRole(['CLUB_COORDINATOR']), isAuthenticated, controller.getClubCoordinatorAnalytics); +router.get('/club-coordinator', isAuthenticated, authorizeRole(['CLUB_COORDINATOR']), controller.getClubCoordinatorAnalytics); // Route to get analytics for students router.get('/student', isAuthenticated,authorizeRole(['STUDENT']), controller.getStudentAnalytics); From d233f285ee31355ff84192cd03425fc199299c6b Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:27:53 +0530 Subject: [PATCH 23/31] fix(frontend): remove dead service calls for /auth/google/register and /fetch (cherry picked from commit ba239cdeca576828dc4e0f93de03b0aa5e078811) --- frontend/src/services/auth.js | 15 --------------- frontend/src/services/utils.js | 13 ------------- 2 files changed, 28 deletions(-) delete mode 100644 frontend/src/services/utils.js diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js index 9da8f6d5..74b91461 100644 --- a/frontend/src/services/auth.js +++ b/frontend/src/services/auth.js @@ -32,21 +32,6 @@ export async function loginUser(email, password) { } } -export async function registerStudentId(id, ID_No) { - try { - const res = await api.post("/auth/google/register", { - token: id, - ID_No, - }); - return res.data || null; - } catch (error) { - console.error( - "Error registering student ID:", - error.response?.data || error.message, - ); - return null; - } -} export async function logoutUser() { try { await api.post("/auth/logout"); diff --git a/frontend/src/services/utils.js b/frontend/src/services/utils.js deleted file mode 100644 index a2a0add6..00000000 --- a/frontend/src/services/utils.js +++ /dev/null @@ -1,13 +0,0 @@ -import api from "../utils/api"; -export async function fetchStudent(student_ID) { - try { - const response = await api.post("/fetch", { student_ID }); - return response.data; // Axios automatically parses JSON - } catch (error) { - console.error( - "Error fetching student:", - error.response?.data || error.message, - ); - return null; - } -} From 526156616527a1637717961dd0b697694eb1a1bd Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:27:56 +0530 Subject: [PATCH 24/31] fix(frontend): repair legacy home dashboard links and stats fetch (cherry picked from commit 55ccf48bc0a22fbe8ebae7b28dc7c960508a9c39) --- frontend/src/Components/OldComponents/Home.js | 13 ++----------- frontend/src/routes/AdminRoutes.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/frontend/src/Components/OldComponents/Home.js b/frontend/src/Components/OldComponents/Home.js index afe01475..15667cce 100644 --- a/frontend/src/Components/OldComponents/Home.js +++ b/frontend/src/Components/OldComponents/Home.js @@ -4,7 +4,6 @@ import { AdminContext } from "../../context/AdminContext"; import api from "../../utils/api"; import { User, - Eye, MessageSquare, Calendar, LogOut, @@ -18,7 +17,6 @@ import { Menu, X, ChevronRight, - ScanSearch, Trophy, } from "lucide-react"; @@ -79,7 +77,6 @@ const StudentDashboard = () => { const navigationItems = [ { id: "profile", label: "Profile Page", icon: User, to: "/profile" }, - { id: "cosa-view", label: "COSA View", icon: Eye, to: "/cosa" }, { id: "feedback", label: "Give Feedback", @@ -106,12 +103,6 @@ const StudentDashboard = () => { icon: Trophy, to: "/view-achievements", }, - { - id: "view-feedback", - label: "View Feedback", - icon: ScanSearch, - to: "/viewfeedback", - }, { id: "logout", label: "Logout", @@ -134,7 +125,7 @@ const StudentDashboard = () => { await Promise.all([ api.get(`/api/skills/user-skills/${isUserLoggedIn._id}`), api.get(`/api/achievements/${isUserLoggedIn._id}`), - api.get(`/api/positions/${isUserLoggedIn._id}`), + api.post(`/api/positions/${isUserLoggedIn._id}`), api.get(`/api/feedback/${isUserLoggedIn._id}`), ]); @@ -181,7 +172,7 @@ const StudentDashboard = () => { const quickActions = [ { label: "Add Achievement", icon: Award, to: "/add-achievement" }, - { label: "New Position", icon: Star, to: "/add-position" }, + { label: "Manage Positions", icon: Star, to: "/manage-position" }, { label: "Give Feedback", icon: MessageSquare, to: "/feedback" }, ]; diff --git a/frontend/src/routes/AdminRoutes.js b/frontend/src/routes/AdminRoutes.js index bfe9f4e6..d5bd4a7e 100644 --- a/frontend/src/routes/AdminRoutes.js +++ b/frontend/src/routes/AdminRoutes.js @@ -15,6 +15,7 @@ import { import CreateOrgUnit from "../Components/organization/CreateOrgUnit"; import EventForm from "../Components/Events/EventForm"; import ClubDashboard from "../Components/OldComponents/ClubCoorinatorDashboard"; +import ViewFeedback from "../Components/Feedback/ViewFeedback"; export const getAdminRoutes = () => [ // GenSec Dashboard routes @@ -85,6 +86,17 @@ export const getAdminRoutes = () => [ } />, + // View Feedback (admin-only; was wrongly public before) + + + + } + />, + // COSA routes Date: Sat, 15 Aug 2026 10:27:56 +0530 Subject: [PATCH 25/31] build(frontend): compile Tailwind v4 via CLI before build so utilities ship (cherry picked from commit fb807e0047b87ab71ce0fb8a233be1a6e73afa51) --- .gitignore | 3 +++ frontend/package.json | 4 +++- frontend/src/App.css | 5 ----- frontend/src/index.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index d042671c..48296bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ /build frontend/build/ +# generated by the tailwind build step +frontend/src/tailwind.css + # new production logs production.log diff --git a/frontend/package.json b/frontend/package.json index 0de56985..13c0464f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,8 +37,10 @@ "web-vitals": "^2.1.4" }, "scripts": { - "tailwind": "tailwindcss", + "tailwind": "tailwindcss -i ./src/index.css -o ./src/tailwind.css --minify", + "prestart": "npm run tailwind", "start": "react-scripts start", + "prebuild": "npm run tailwind", "build": "react-scripts build", "test": "react-scripts test", "lint": "eslint src", diff --git a/frontend/src/App.css b/frontend/src/App.css index 984cbac1..6000d89a 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,8 +1,3 @@ -/* @tailwind base; -@tailwind components; -@tailwind utilities; */ -@import "tailwindcss"; - .App { text-align: center; } diff --git a/frontend/src/index.js b/frontend/src/index.js index c5bab643..afe019fe 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,6 +1,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import "./index.css"; +import "./tailwind.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; import "bootstrap/dist/css/bootstrap.min.css"; From 402801a186105693e8de5239a06dcb7920d2cc80 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:41:30 +0530 Subject: [PATCH 26/31] fix(backend): remove duplicate /api/announcements mount (cherry picked from commit 766d3b1a35dc9010ee488d56f534067dc47a6c79) --- backend/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/index.js b/backend/index.js index 26b64c17..13fac431 100644 --- a/backend/index.js +++ b/backend/index.js @@ -86,7 +86,6 @@ app.use("/api/positions", positionsRoutes); app.use("/api/orgUnit", organizationalUnitRoutes); app.use("/api/announcements", announcementRoutes); app.use("/api/dashboard", dashboardRoutes); -app.use("/api/announcements", announcementRoutes); app.use("/api/analytics", analyticsRoutes); app.use("/api/rooms", roomBookingRoutes); app.use("/api/por", porRoutes); From f80438de5f125bdce196145825e062c75d95d7f2 Mon Sep 17 00:00:00 2001 From: ashish-kumar-dash Date: Sat, 15 Aug 2026 10:47:28 +0530 Subject: [PATCH 27/31] feat(backend): upload certificates to Cloudinary and generate before approving (cherry picked from commit b837fc85dbc49e596fa301d583d550700faa6418) --- backend/Dockerfile | 10 +++++++++- backend/controllers/certificateBatchController.js | 15 ++++++++------- backend/utils/renderPdf.js | 7 ++++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 0ad48cf8..2f99e125 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,6 +6,9 @@ RUN apk update && apk upgrade --no-cache COPY package.json package-lock.json ./ +# Puppeteer's Chrome is installed in the runtime stage via apk. +ENV PUPPETEER_SKIP_DOWNLOAD=true + RUN npm ci COPY . . @@ -17,7 +20,9 @@ FROM node:18-alpine WORKDIR /app -RUN apk update && apk upgrade --no-cache +RUN apk update && apk upgrade --no-cache \ + && apk add --no-cache chromium \ + && rm -rf /var/cache/apk/* COPY --from=builder /app ./ @@ -25,6 +30,9 @@ RUN addgroup -S appgroup && adduser -S appuser -G appgroup && chown -R appuser:a USER appuser +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium +ENV PUPPETEER_NO_SANDBOX=true + EXPOSE 8000 CMD ["node", "index.js"] diff --git a/backend/controllers/certificateBatchController.js b/backend/controllers/certificateBatchController.js index 4f3c6fae..2e838501 100644 --- a/backend/controllers/certificateBatchController.js +++ b/backend/controllers/certificateBatchController.js @@ -544,6 +544,13 @@ async function approveBatch(req, res) { }; } + // Final (President) approval: generate certificates BEFORE marking the + // batch Active/Approved. If generation fails, the batch stays Submitted + // so the approval can be retried once the cause is fixed. + if (level === 1) { + await generateCertificates(batch); + } + const updatedBatch = await CertificateBatch.findOneAndUpdate( matchQuery, update, @@ -557,17 +564,11 @@ async function approveBatch(req, res) { }); } - if (level === 1) { - // Final (President) approval just happened - generate certificates - // exactly once, from the freshly-updated, level===2 document. - await generateCertificates(updatedBatch); - } - return res.status(200).json({ message: level === 0 ? "Batch approved by GENSEC. Forwarded to President." - : "Batch approved successfully. Certificates are being generated.", + : "Batch approved successfully. Certificates generated.", }); } catch (err) { if (err instanceof HttpError) { diff --git a/backend/utils/renderPdf.js b/backend/utils/renderPdf.js index f78d2517..3d2a1dff 100644 --- a/backend/utils/renderPdf.js +++ b/backend/utils/renderPdf.js @@ -33,7 +33,12 @@ const watermarkLogoDataUri = loadAsDataUri( try { if (!browser) { - browser = await puppeteer.launch({ headless: true }); + browser = await puppeteer.launch({ + headless: true, + ...(process.env.PUPPETEER_NO_SANDBOX === "true" + ? { args: ["--no-sandbox", "--disable-setuid-sandbox"] } + : {}), + }); ownBrowser = true; } From c6cb516969ad21f758ab8a5961612b39ebed08e7 Mon Sep 17 00:00:00 2001 From: UtkarshUmap Date: Sun, 16 Aug 2026 19:44:09 +0530 Subject: [PATCH 28/31] Refactor renderPdf.js to use dynamic import for Puppeteer (cherry picked from commit 3cf270a66388e2d2c0286eaa7523f11698ca7af1) --- backend/utils/renderPdf.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/utils/renderPdf.js b/backend/utils/renderPdf.js index 3d2a1dff..feb7bbc0 100644 --- a/backend/utils/renderPdf.js +++ b/backend/utils/renderPdf.js @@ -1,4 +1,3 @@ -const puppeteer = require("puppeteer"); const handlebars = require("handlebars"); const fs = require("fs"); const path = require("path"); @@ -27,6 +26,8 @@ const watermarkLogoDataUri = loadAsDataUri( ); async function renderToPdf(data, sharedBrowser = null) { + const puppeteer = (await import("puppeteer")).default; + let browser = sharedBrowser; let ownBrowser = false; let page; @@ -95,4 +96,4 @@ const watermarkLogoDataUri = loadAsDataUri( } } -module.exports = renderToPdf; \ No newline at end of file +module.exports = renderToPdf; From 94cb11072d9d21bcb49feab524491d3a76325e9e Mon Sep 17 00:00:00 2001 From: UtkarshUmap Date: Sun, 16 Aug 2026 19:46:17 +0530 Subject: [PATCH 29/31] Refactor certificate generation service (cherry picked from commit 368e5208c4294bc26ab1b601115e519b27ad57dc) --- backend/services/certificates.service.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/services/certificates.service.js b/backend/services/certificates.service.js index 4d009109..2da773c1 100644 --- a/backend/services/certificates.service.js +++ b/backend/services/certificates.service.js @@ -1,4 +1,3 @@ -const puppeteer = require("puppeteer"); const crypto = require("crypto"); const { User } = require("../models/schema"); const renderToPdf = require("../utils/renderPdf"); @@ -7,6 +6,8 @@ const { Certificate } = require("../models/certificateSchema"); const Template = require("../models/templateSchema"); async function generateCertificates(batch) { + const puppeteer = (await import("puppeteer")).default; + const users = await User.find({ _id: { $in: batch.users }, }).select("personal_info"); @@ -121,4 +122,4 @@ async function generateCertificates(batch) { } } -module.exports = generateCertificates; \ No newline at end of file +module.exports = generateCertificates; From cda1989e907b347101b68a95028644f8db23e5fd Mon Sep 17 00:00:00 2001 From: UtkarshUmap Date: Sun, 16 Aug 2026 23:06:25 +0530 Subject: [PATCH 30/31] Integrate MongoDB session store and update cookie settings Added MongoDB session store and updated session configuration for HTTPS handling. (cherry picked from commit 45374ca7ba68d27c8c0a3ff15bd8f4fd64a3c68d) --- backend/index.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/backend/index.js b/backend/index.js index 13fac431..ca185910 100644 --- a/backend/index.js +++ b/backend/index.js @@ -30,9 +30,16 @@ const taskRoutes = require("./routes/task.routes.js"); const studentsRoutes = require("./routes/students.js"); +const MongoDBStore = require("connect-mongodb-session")(session); + const app = express(); -if (process.env.NODE_ENV === "production") { +const isHostedOverHttps = + (process.env.FRONTEND_URL || "").startsWith("https://") || + process.env.NODE_ENV === "production"; + +if (isHostedOverHttps) { + // Required behind Render's proxy, otherwise `secure` cookies are never sent. app.set("trust proxy", 1); } @@ -51,14 +58,25 @@ if (!process.env.JWT_SECRET_TOKEN) { throw new Error("JWT_SECRET_TOKEN environment variable is required"); } +const sessionStore = new MongoDBStore({ + uri: process.env.MONGODB_URI, + collection: "sessions", +}); + +sessionStore.on("error", (error) => { + console.error("Session store error:", error); +}); + app.use( session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, + store: sessionStore, cookie: { - secure: process.env.NODE_ENV === "production", // HTTPS only in prod - sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", // cross-origin in prod, + secure: isHostedOverHttps, // HTTPS only when hosted + sameSite: isHostedOverHttps ? "none" : "lax", // cross-site when hosted + maxAge: 7 * 24 * 60 * 60 * 1000, }, }), ); From 344ebfa9f53c9699ab8ad134f793da237b7f74cd Mon Sep 17 00:00:00 2001 From: KotapatiSaiMounika Date: Sat, 3 Oct 2026 05:40:59 +0000 Subject: [PATCH 31/31] chore(backend): allow dynamic import() in eslint config --- backend/package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 38e661ec..657b043e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -33,7 +33,7 @@ "parserOptions": { "ecmaVersion": 2022 }, - "rules": { + "rules": { "node/no-unpublished-require": [ "error", { @@ -42,6 +42,10 @@ "mongodb-memory-server" ] } + ], + "node/no-unsupported-features/es-syntax": [ + "error", + { "ignores": ["dynamicImport"] } ] } },