From 416746c4a7f463c53eb63ad091480d291b81bc60 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Sat, 6 Jun 2026 15:45:43 +0530 Subject: [PATCH 1/6] Real-time referral message updates in UI --- backend/utils/socket.js | 30 ++++++++++++++++ frontend/src/App.jsx | 9 +++-- frontend/src/SocketContext.jsx | 41 ++++++++++++++++++++++ frontend/src/components/ReferralDetail.jsx | 24 +++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/backend/utils/socket.js b/backend/utils/socket.js index 994316b..eaa0fb6 100644 --- a/backend/utils/socket.js +++ b/backend/utils/socket.js @@ -316,6 +316,36 @@ const initializeSocket = (io) => { } }); + // ===== REFERRAL: JOIN/LEAVE ROOMS ===== + socket.on('joinReferral', (data) => { + try { + const { referralId } = data || {}; + if (!referralId) { + socket.emit('error', { message: 'referralId is required' }); + return; + } + + const roomId = `referral:${referralId}`; + socket.join(roomId); + console.log(`Socket ${socket.id} joined referral room ${roomId}`); + } catch (error) { + console.error('Error in joinReferral:', error); + } + }); + + socket.on('leaveReferral', (data) => { + try { + const { referralId } = data || {}; + if (!referralId) return; + + const roomId = `referral:${referralId}`; + socket.leave(roomId); + console.log(`Socket ${socket.id} left referral room ${roomId}`); + } catch (error) { + console.error('Error in leaveReferral:', error); + } + }); + // ===== DISCONNECT HANDLER ===== socket.on('disconnect', () => { try { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4765d0e..e2f8809 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom"; import { ToastContainer } from "react-toastify"; import { AuthProvider, useAuth } from "./AuthContext"; import { ThemeProvider } from "./ThemeContext"; +import { SocketProvider } from "./SocketContext"; import ScrollToTop from "./components/ScrollToTop"; import SmoothWheelScroll from "./components/SmoothWheelScroll"; import PrivateRoute from "./components/PrivateRoute"; @@ -77,9 +78,11 @@ function App() { - - - + + + + + diff --git a/frontend/src/SocketContext.jsx b/frontend/src/SocketContext.jsx index 334060b..f93edc1 100644 --- a/frontend/src/SocketContext.jsx +++ b/frontend/src/SocketContext.jsx @@ -98,6 +98,29 @@ export const SocketProvider = ({ children }) => { console.log(`Left conversation: ${conversationId}`); }, [socket]); + /** + * Join a referral room + */ + const joinReferral = useCallback((referralId) => { + if (!socket || !isConnected) { + console.warn('Socket not connected'); + return; + } + + socket.emit('joinReferral', { referralId }); + console.log(`Joined referral room: referral:${referralId}`); + }, [socket, isConnected]); + + /** + * Leave a referral room + */ + const leaveReferral = useCallback((referralId) => { + if (!socket) return; + + socket.emit('leaveReferral', { referralId }); + console.log(`Left referral room: referral:${referralId}`); + }, [socket]); + /** * Mark messages as read */ @@ -242,6 +265,8 @@ export const SocketProvider = ({ children }) => { typingUsers, joinConversation, leaveConversation, + joinReferral, + leaveReferral, markAsRead, addReaction, removeReaction, @@ -260,6 +285,8 @@ export const SocketProvider = ({ children }) => { typingUsers, joinConversation, leaveConversation, + joinReferral, + leaveReferral, markAsRead, addReaction, removeReaction, @@ -289,4 +316,18 @@ export const useSocket = () => { return context; }; +/** + * Hook to listen to a socket event + */ +export const useSocketListener = (event, callback) => { + const { on } = useSocket(); + + useEffect(() => { + const cleanup = on(event, callback); + return () => { + if (cleanup) cleanup(); + }; + }, [event, callback, on]); +}; + export default SocketContext; diff --git a/frontend/src/components/ReferralDetail.jsx b/frontend/src/components/ReferralDetail.jsx index 31c74f8..038fd47 100644 --- a/frontend/src/components/ReferralDetail.jsx +++ b/frontend/src/components/ReferralDetail.jsx @@ -3,12 +3,36 @@ import { useParams, Link, useNavigate } from 'react-router-dom'; import apiClient from '../api/client'; import { useAuth } from '../AuthContext'; import { toast } from 'react-toastify'; +import { useSocket, useSocketListener } from '../SocketContext'; const ReferralDetail = () => { const { id } = useParams(); const navigate = useNavigate(); const { isAuthReady, user: authUser } = useAuth(); const [referral, setReferral] = useState(null); + + const { joinReferral, leaveReferral } = useSocket(); + + useEffect(() => { + if (id) { + joinReferral(id); + return () => { + leaveReferral(id); + }; + } + }, [id, joinReferral, leaveReferral]); + + useSocketListener('referralMessageCreated', (payload) => { + if (payload && String(payload.referralId) === String(id)) { + setMessages((prevMessages) => { + const messageExists = prevMessages.some((msg) => msg._id === payload.message?._id); + if (messageExists) { + return prevMessages; + } + return [...prevMessages, payload.message]; + }); + } + }); const [timeline, setTimeline] = useState([]); const [messages, setMessages] = useState([]); const [messageBody, setMessageBody] = useState(''); From 7d510b0d1d69fc4b34a3b4336eb11cdecdefd172 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Sat, 6 Jun 2026 15:49:49 +0530 Subject: [PATCH 2/6] [ENHANCE]: Moderation actions should invalidate caches / refresh referral list --- backend/controllers/referral.controller.js | 36 +++++++++++++ frontend/src/admin/AdminReferrals.jsx | 62 +++++++++++++++++++++- frontend/src/components/ReferralList.jsx | 19 +++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/backend/controllers/referral.controller.js b/backend/controllers/referral.controller.js index bc9687d..b6096ac 100644 --- a/backend/controllers/referral.controller.js +++ b/backend/controllers/referral.controller.js @@ -561,6 +561,15 @@ async function flagReferralForSpam(req, res, next) { } }); + const io = req.app.get('io'); + if (io) { + io.emit('referralModerationUpdated', { + referralId: id, + status: referral.moderation?.status || 'visible', + referral + }); + } + res.json({ message: 'Referral flagged successfully', referral, @@ -594,6 +603,15 @@ async function hideReferral(req, res, next) { } }); + const io = req.app.get('io'); + if (io) { + io.emit('referralModerationUpdated', { + referralId: id, + status: referral.moderation?.status || 'visible', + referral + }); + } + res.json({ message: 'Referral hidden successfully', referral, @@ -627,6 +645,15 @@ async function restoreReferral(req, res, next) { } }); + const io = req.app.get('io'); + if (io) { + io.emit('referralModerationUpdated', { + referralId: id, + status: referral.moderation?.status || 'visible', + referral + }); + } + res.json({ message: 'Referral restored successfully', referral, @@ -672,6 +699,15 @@ async function suspendReferralPoster(req, res, next) { } }); + const io = req.app.get('io'); + if (io) { + io.emit('referralModerationUpdated', { + referralId: id, + status: referral.moderation?.status || 'visible', + referral + }); + } + res.json({ message: 'Referral poster suspended successfully', referral, diff --git a/frontend/src/admin/AdminReferrals.jsx b/frontend/src/admin/AdminReferrals.jsx index 53d9168..ffcec27 100644 --- a/frontend/src/admin/AdminReferrals.jsx +++ b/frontend/src/admin/AdminReferrals.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import apiClient from '../api/client'; +import { useSocketListener } from '../SocketContext'; const statusColors = { pending: 'warning', @@ -39,6 +40,27 @@ const AdminReferrals = () => { const [moderationActions, setModerationActions] = useState([]); const [auditLoading, setAuditLoading] = useState(false); + useSocketListener('referralModerationUpdated', (payload) => { + if (payload) { + const { referralId, status, referral: updatedReferral } = payload; + setModerationReferrals((currentList) => + currentList.map((item) => { + if (item._id !== referralId) return item; + + const updatedPostedBy = updatedReferral?.postedBy + ? { ...item.postedBy, ...updatedReferral.postedBy } + : item.postedBy; + + return { + ...item, + postedBy: updatedPostedBy, + moderation: { ...item.moderation, status } + }; + }) + ); + } + }); + useEffect(() => { fetchJobReferrals(); fetchModerationReferrals(); @@ -115,9 +137,45 @@ const AdminReferrals = () => { return; } + const trimmedReason = reason.trim() || defaultReason || ''; + + // Save current list state for fallback + const previousModerationReferrals = [...moderationReferrals]; + + // Optimistically update the UI + setModerationReferrals((currentList) => + currentList.map((item) => { + if (item._id !== referralId) return item; + + let newStatus = item.moderation?.status || 'visible'; + let posterSuspended = item.postedBy?.referralPostingSuspended || false; + + if (actionType === 'flag') { + newStatus = 'flagged'; + } else if (actionType === 'hide') { + newStatus = 'hidden'; + } else if (actionType === 'restore') { + newStatus = 'visible'; + } else if (actionType === 'suspend-poster') { + newStatus = 'hidden'; + posterSuspended = true; + } + + return { + ...item, + postedBy: item.postedBy ? { ...item.postedBy, referralPostingSuspended: posterSuspended } : null, + moderation: { + ...item.moderation, + status: newStatus, + reason: trimmedReason + } + }; + }) + ); + try { await apiClient.post(`/admin/referrals/${referralId}/${actionType}`, { - reason: reason.trim() || defaultReason || '' + reason: trimmedReason }); await Promise.all([ @@ -127,6 +185,8 @@ const AdminReferrals = () => { fetchModerationActions(referralId); } catch (err) { + // Roll back on error + setModerationReferrals(previousModerationReferrals); alert(err.response?.data?.message || 'Failed to update moderation state'); } }; diff --git a/frontend/src/components/ReferralList.jsx b/frontend/src/components/ReferralList.jsx index d60396b..7134119 100644 --- a/frontend/src/components/ReferralList.jsx +++ b/frontend/src/components/ReferralList.jsx @@ -4,10 +4,29 @@ import { FaBookmark, FaRegBookmark } from 'react-icons/fa'; import apiClient from '../api/client'; import { useAuth } from '../AuthContext'; import { toast } from 'react-toastify'; +import { useSocketListener } from '../SocketContext'; const ReferralList = () => { const { user } = useAuth(); const [referrals, setReferrals] = useState([]); + + useSocketListener('referralModerationUpdated', (payload) => { + if (payload) { + const { referralId, status } = payload; + if (status === 'hidden' || status === 'removed') { + setReferrals((prevReferrals) => prevReferrals.filter(r => r._id !== referralId)); + } else { + setReferrals((prevReferrals) => + prevReferrals.map((r) => + r._id === referralId + ? { ...r, moderation: { ...r.moderation, status } } + : r + ) + ); + } + } + }); + const [pagination, setPagination] = useState({ total: 0, page: 1, limit: 10 }); const [loading, setLoading] = useState(true); const [loadingNextPage, setLoadingNextPage] = useState(false); From 657c533917ae7d99b5a327d0ee90c40154394413 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Sat, 6 Jun 2026 15:54:00 +0530 Subject: [PATCH 3/6] [ENHANCE]: Resume analyzer: improve skill matching explainability --- backend/controllers/resumeAnalyzer.controller.js | 7 +++++++ backend/models/ResumeAnalysis.model.js | 5 +++++ frontend/src/components/AnalysisResults.jsx | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/backend/controllers/resumeAnalyzer.controller.js b/backend/controllers/resumeAnalyzer.controller.js index c7985ee..692015c 100644 --- a/backend/controllers/resumeAnalyzer.controller.js +++ b/backend/controllers/resumeAnalyzer.controller.js @@ -104,11 +104,18 @@ function buildSkillExplanations({ resumeText = '', resumeSkills = [], requiredSk matchedBecause.push(`resume does not include '${skillLabel}'`); } + const confidence = matchedType === 'exact' ? 'exact' : (matchedType === 'partial' ? 'partial' : 'low'); + + if (matchedBecause.length === 0) { + matchedBecause.push(`no confirmation information available for '${skillLabel}'`); + } + return { skill: skillLabel, status, matchedType, matchedBecause, + confidence, source, }; }); diff --git a/backend/models/ResumeAnalysis.model.js b/backend/models/ResumeAnalysis.model.js index de11c86..63e96bc 100644 --- a/backend/models/ResumeAnalysis.model.js +++ b/backend/models/ResumeAnalysis.model.js @@ -60,6 +60,11 @@ const resumeAnalysisSchema = new mongoose.Schema( type: String, default: 'jobRequirements', }, + confidence: { + type: String, + enum: ['exact', 'partial', 'low'], + default: 'low', + }, }, ], default: [], diff --git a/frontend/src/components/AnalysisResults.jsx b/frontend/src/components/AnalysisResults.jsx index 1160013..17aa507 100644 --- a/frontend/src/components/AnalysisResults.jsx +++ b/frontend/src/components/AnalysisResults.jsx @@ -74,6 +74,7 @@ const SimpleBarChart = ({ matched, missing }) => { const SkillExplanationRow = ({ item }) => { const isMatched = item?.status === 'matched'; const matchedTypeLabel = item?.matchedType || 'none'; + const confidence = item?.confidence || (isMatched ? (matchedTypeLabel === 'exact' ? 'exact' : 'partial') : 'low'); const toneClass = isMatched ? 'border-green-200 bg-green-50 text-green-800' : 'border-red-200 bg-red-50 text-red-800'; @@ -96,6 +97,13 @@ const SkillExplanationRow = ({ item }) => { {matchedTypeLabel} + + {confidence.charAt(0).toUpperCase() + confidence.slice(1)} Confidence + @@ -119,6 +127,7 @@ const SkillExplanationsPanel = ({ analysis }) => { skill, status: 'matched', matchedType: 'exact', + confidence: 'exact', matchedBecause: [`Analysis reported '${skill}' as matched.`], source: 'analysis', })), @@ -126,6 +135,7 @@ const SkillExplanationsPanel = ({ analysis }) => { skill, status: 'missing', matchedType: 'none', + confidence: 'low', matchedBecause: [`Analysis reported '${skill}' as missing.`], source: 'analysis', })), From b4153bcf2ddf9166e904fec5842a388eeb4d1db1 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Sat, 6 Jun 2026 15:57:12 +0530 Subject: [PATCH 4/6] [ENHANCE]: Add input validation for referral message payload --- backend/controllers/referral.controller.js | 35 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/backend/controllers/referral.controller.js b/backend/controllers/referral.controller.js index b6096ac..f14b313 100644 --- a/backend/controllers/referral.controller.js +++ b/backend/controllers/referral.controller.js @@ -749,6 +749,36 @@ async function sendReferralMessage(req, res, next) { try { const { id } = req.params; const { body, recipientId } = req.body; + + const validationErrors = {}; + + // Validate referral ID (id) + if (!mongoose.Types.ObjectId.isValid(id)) { + validationErrors.id = 'Invalid referral ID format'; + } + + // Validate recipientId if provided + if (recipientId !== undefined && recipientId !== null && String(recipientId).trim() !== '') { + if (!mongoose.Types.ObjectId.isValid(recipientId)) { + validationErrors.recipientId = 'Recipient ID must be a valid ObjectId'; + } + } + + // Validate body + const trimmedBody = String(body || '').trim(); + if (!trimmedBody) { + validationErrors.body = 'Message body is required'; + } else if (trimmedBody.length > 2000) { + validationErrors.body = 'Message body cannot exceed 2000 characters'; + } + + if (Object.keys(validationErrors).length > 0) { + return res.status(400).json({ + message: 'Validation failed', + errors: validationErrors + }); + } + const { referral, currentUser, access, isOwner, isApplicant } = await getReferralAccess(id, req.user.id); if (!referral) { @@ -759,11 +789,6 @@ async function sendReferralMessage(req, res, next) { return res.status(403).json({ message: 'Access denied' }); } - const trimmedBody = String(body || '').trim(); - if (!trimmedBody) { - return res.status(400).json({ message: 'Message body is required' }); - } - let recipientUser = null; if (isOwner) { From fad189f46d03a9bc5ba5e4d74a1c19c3943100d2 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Sat, 6 Jun 2026 16:00:07 +0530 Subject: [PATCH 5/6] [REFACTOR]: Extract referral access checks into a reusable middleware --- backend/controllers/referral.controller.js | 111 ++++++++------------- backend/routes/referral.routes.js | 30 +++--- 2 files changed, 56 insertions(+), 85 deletions(-) diff --git a/backend/controllers/referral.controller.js b/backend/controllers/referral.controller.js index f14b313..c14e6c6 100644 --- a/backend/controllers/referral.controller.js +++ b/backend/controllers/referral.controller.js @@ -104,6 +104,30 @@ async function getReferralAccess(referralId, userId) { }; } +async function checkReferralAccessMiddleware(req, res, next) { + try { + const { id } = req.params; + if (!id || !mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ message: 'Invalid referral ID format' }); + } + + const accessInfo = await getReferralAccess(id, req.user.id); + if (!accessInfo.referral) { + return res.status(404).json({ message: 'Referral not found' }); + } + + // Hide referrals that are hidden/removed from non-admins + if (isReferralHidden(accessInfo.referral) && accessInfo.currentUser?.type !== 'admin') { + return res.status(404).json({ message: 'Referral not found' }); + } + + req.referralAccess = accessInfo; + next(); + } catch (err) { + next(err); + } +} + // Create a new job referral opportunity async function createReferral(req, res, next) { try { @@ -223,22 +247,12 @@ async function getReferrals(req, res, next) { // Apply for a referral async function applyForReferral(req, res, next) { try { - const { id } = req.params; - const currentUser = await getCurrentUserSummary(req.user.id); + const { referral, currentUser } = req.referralAccess; if (!currentUser || currentUser.type !== 'student') { return res.status(403).json({ message: 'Only students can apply for referrals' }); } - const referral = await JobReferral.findById(id); - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } - - if (isReferralHidden(referral) && currentUser?.type !== 'admin') { - return res.status(404).json({ message: 'Referral not found' }); - } - if (referral.status !== 'open') { return res.status(400).json({ message: 'Referral is no longer open' }); } @@ -287,16 +301,9 @@ async function applyForReferral(req, res, next) { // Accept a referral applicant async function acceptReferral(req, res, next) { try { - const { id, applicantId } = req.params; - const currentUser = await getCurrentUserSummary(req.user.id); + const { applicantId } = req.params; + const { referral, isOwner, isAdmin, currentUser } = req.referralAccess; - const referral = await JobReferral.findById(id); - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } - - const isOwner = referral.postedBy.toString() === req.user.id; - const isAdmin = currentUser?.type === 'admin'; if (!isOwner && !isAdmin) { return res.status(403).json({ message: 'Access denied' }); } @@ -346,16 +353,9 @@ async function acceptReferral(req, res, next) { // Reject a referral applicant async function rejectReferral(req, res, next) { try { - const { id, applicantId } = req.params; - const currentUser = await getCurrentUserSummary(req.user.id); - - const referral = await JobReferral.findById(id); - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } + const { applicantId } = req.params; + const { referral, isOwner, isAdmin, currentUser } = req.referralAccess; - const isOwner = referral.postedBy.toString() === req.user.id; - const isAdmin = currentUser?.type === 'admin'; if (!isOwner && !isAdmin) { return res.status(403).json({ message: 'Access denied' }); } @@ -399,15 +399,10 @@ async function rejectReferral(req, res, next) { // Get single referral by ID async function getReferralById(req, res, next) { try { - const { referral, access } = await getReferralAccess(req.params.id, req.user.id); - - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } + const { referral, access } = req.referralAccess; if (!access) { - // Do not leak referral existence. - return res.status(404).json({ message: 'Referral not found' }); + return res.status(403).json({ message: 'Access denied' }); } // Keep response structure consistent with prior implementation. @@ -422,15 +417,10 @@ async function getReferralById(req, res, next) { async function getReferralTimeline(req, res, next) { try { - const { referral, access } = await getReferralAccess(req.params.id, req.user.id); - - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } + const { referral, access } = req.referralAccess; if (!access) { - // Do not leak referral existence. - return res.status(404).json({ message: 'Referral not found' }); + return res.status(403).json({ message: 'Access denied' }); } const timeline = [...(referral.timeline || [])].sort( @@ -445,18 +435,9 @@ async function getReferralTimeline(req, res, next) { async function closeReferral(req, res, next) { - try { - const { id } = req.params; - const currentUser = await getCurrentUserSummary(req.user.id); + const { referral, isOwner, isAdmin, currentUser } = req.referralAccess; - const referral = await JobReferral.findById(id); - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } - - const isOwner = referral.postedBy.toString() === req.user.id; - const isAdmin = currentUser?.type === 'admin'; if (!isOwner && !isAdmin) { return res.status(403).json({ message: 'Access denied' }); } @@ -724,11 +705,7 @@ async function suspendReferralPoster(req, res, next) { async function getReferralMessages(req, res, next) { try { const { id } = req.params; - const { referral, access } = await getReferralAccess(id, req.user.id); - - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } + const { referral, access } = req.referralAccess; if (!access) { return res.status(403).json({ message: 'Access denied' }); @@ -779,11 +756,7 @@ async function sendReferralMessage(req, res, next) { }); } - const { referral, currentUser, access, isOwner, isApplicant } = await getReferralAccess(id, req.user.id); - - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } + const { referral, currentUser, access, isOwner, isApplicant } = req.referralAccess; if (!access) { return res.status(403).json({ message: 'Access denied' }); @@ -868,13 +841,7 @@ async function getMyReferrals(req, res, next) { async function computeReferralBonus(req, res, next) { try { - const referral = await JobReferral.findById(req.params.id) - .populate('postedBy', 'name email alumnus_bio') - .populate('applicants.user', 'name email alumnus_bio'); - - if (!referral) { - return res.status(404).json({ message: 'Referral not found' }); - } + const { referral } = req.referralAccess; const { referral: persistedReferral, bonus } = await recomputeAndPersistReferralBonus(referral, { computedBy: req.user?.id || null @@ -908,6 +875,6 @@ module.exports = { getMyReferrals, getReferralById, getReferralTimeline, - computeReferralBonus + computeReferralBonus, + checkReferralAccessMiddleware }; - diff --git a/backend/routes/referral.routes.js b/backend/routes/referral.routes.js index 1fe0b72..d32acde 100644 --- a/backend/routes/referral.routes.js +++ b/backend/routes/referral.routes.js @@ -11,7 +11,8 @@ const { getMyReferrals, getReferralById, getReferralTimeline, - computeReferralBonus + computeReferralBonus, + checkReferralAccessMiddleware } = require('../controllers/referral.controller'); const { authenticate, isStudent } = require('../middlewares/auth.middleware'); @@ -35,31 +36,34 @@ router.post('/', authenticate, createReferral); // Get all referrals router.get('/', authenticate, getReferrals); +// Get my referrals +router.get('/my-referrals', authenticate, getMyReferrals); + +// Check access for all ID-based subroutes +router.use('/:id', authenticate, checkReferralAccessMiddleware); + // Apply for referral -router.post('/:id/apply', authenticate, isStudent, applyForReferral); +router.post('/:id/apply', isStudent, applyForReferral); // Referral Q&A / messaging -router.post('/:id/messages', authenticate, sendReferralMessage); -router.get('/:id/messages', authenticate, getReferralMessages); +router.post('/:id/messages', sendReferralMessage); +router.get('/:id/messages', getReferralMessages); // Referral bonus computation -router.post('/:id/compute-bonus', authenticate, allowBonusCompute, computeReferralBonus); +router.post('/:id/compute-bonus', allowBonusCompute, computeReferralBonus); // Manage applicants (only poster) -router.put('/:id/applicants/:applicantId/accept', authenticate, acceptReferral); -router.put('/:id/applicants/:applicantId/reject', authenticate, rejectReferral); +router.put('/:id/applicants/:applicantId/accept', acceptReferral); +router.put('/:id/applicants/:applicantId/reject', rejectReferral); // Close referral (poster/admin) -router.patch('/:id/close', authenticate, closeReferral); +router.patch('/:id/close', closeReferral); // Get referral timeline -router.get('/:id/timeline', authenticate, getReferralTimeline); - -// Get my referrals -router.get('/my-referrals', authenticate, getMyReferrals); +router.get('/:id/timeline', getReferralTimeline); // Get single referral -router.get('/:id', authenticate, getReferralById); +router.get('/:id', getReferralById); module.exports = router; From d8a7a609298dab8693a7eb7fc92a959d45bef6d1 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Sat, 6 Jun 2026 16:04:31 +0530 Subject: [PATCH 6/6] [REFACTOR]: Remove duplicated moderation reason normalization --- backend/controllers/referral.controller.js | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/backend/controllers/referral.controller.js b/backend/controllers/referral.controller.js index c14e6c6..3e972f4 100644 --- a/backend/controllers/referral.controller.js +++ b/backend/controllers/referral.controller.js @@ -69,7 +69,7 @@ async function moderateReferral({ referralId, adminId, actionType, reason, refer adminId, referralId: referral._id, actionType, - reason: normalizeModerationReason(reason), + reason: reason, targetUserId: referral.postedBy?._id || referral.postedBy || null }); }); @@ -526,16 +526,17 @@ async function flagReferralForSpam(req, res, next) { try { const { id } = req.params; const { reason } = req.body; + const normalizedReason = normalizeModerationReason(reason, 'Flagged for review'); const { referral, moderationAction } = await moderateReferral({ referralId: id, adminId: req.user.id, actionType: 'flag', - reason: normalizeModerationReason(reason, 'Flagged for review'), + reason: normalizedReason, referralMutator: async (referralDoc) => { referralDoc.moderation = { status: 'flagged', - reason: normalizeModerationReason(reason, 'Flagged for review'), + reason: normalizedReason, moderatedBy: req.user.id, moderatedAt: new Date() }; @@ -568,16 +569,17 @@ async function hideReferral(req, res, next) { try { const { id } = req.params; const { reason } = req.body; + const normalizedReason = normalizeModerationReason(reason, 'Hidden by administrator'); const { referral, moderationAction } = await moderateReferral({ referralId: id, adminId: req.user.id, actionType: 'hide', - reason: normalizeModerationReason(reason, 'Hidden by administrator'), + reason: normalizedReason, referralMutator: async (referralDoc) => { referralDoc.moderation = { status: 'hidden', - reason: normalizeModerationReason(reason, 'Hidden by administrator'), + reason: normalizedReason, moderatedBy: req.user.id, moderatedAt: new Date() }; @@ -610,16 +612,17 @@ async function restoreReferral(req, res, next) { try { const { id } = req.params; const { reason } = req.body; + const normalizedReason = normalizeModerationReason(reason, 'Restored by administrator'); const { referral, moderationAction } = await moderateReferral({ referralId: id, adminId: req.user.id, actionType: 'restore', - reason: normalizeModerationReason(reason, 'Restored by administrator'), + reason: normalizedReason, referralMutator: async (referralDoc) => { referralDoc.moderation = { status: 'visible', - reason: normalizeModerationReason(reason, 'Restored by administrator'), + reason: normalizedReason, moderatedBy: req.user.id, moderatedAt: new Date() }; @@ -652,16 +655,17 @@ async function suspendReferralPoster(req, res, next) { try { const { id } = req.params; const { reason } = req.body; + const normalizedReason = normalizeModerationReason(reason, 'Poster suspended by administrator'); const { referral, moderationAction } = await moderateReferral({ referralId: id, adminId: req.user.id, actionType: 'suspend_poster', - reason: normalizeModerationReason(reason, 'Poster suspended by administrator'), + reason: normalizedReason, referralMutator: async (referralDoc) => { referralDoc.moderation = { status: 'hidden', - reason: normalizeModerationReason(reason, 'Poster suspended by administrator'), + reason: normalizedReason, moderatedBy: req.user.id, moderatedAt: new Date() }; @@ -674,7 +678,7 @@ async function suspendReferralPoster(req, res, next) { await User.findByIdAndUpdate(posterId, { referralPostingSuspended: true, referralPostingSuspendedAt: new Date(), - referralPostingSuspendedReason: normalizeModerationReason(reason, 'Poster suspended by administrator'), + referralPostingSuspendedReason: normalizedReason, referralPostingSuspendedBy: req.user.id }, { session }); }