From 62a0df2df261960211cf21a1c030fe5b21d970f0 Mon Sep 17 00:00:00 2001 From: ritika-290 Date: Tue, 7 Apr 2026 17:38:43 +0530 Subject: [PATCH 01/25] Add peer evaluation scheduler demo code --- .../controllers/evaluationController.js | 57 ++++++++ peer_eval_scheduler_demo/jobs/scheduler.js | 55 ++++++++ .../models/EvaluationTask.js | 24 ++++ .../models/Notification.js | 14 ++ peer_eval_scheduler_demo/models/Student.js | 19 +++ .../services/evaluationService.js | 132 ++++++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 peer_eval_scheduler_demo/controllers/evaluationController.js create mode 100644 peer_eval_scheduler_demo/jobs/scheduler.js create mode 100644 peer_eval_scheduler_demo/models/EvaluationTask.js create mode 100644 peer_eval_scheduler_demo/models/Notification.js create mode 100644 peer_eval_scheduler_demo/models/Student.js create mode 100644 peer_eval_scheduler_demo/services/evaluationService.js diff --git a/peer_eval_scheduler_demo/controllers/evaluationController.js b/peer_eval_scheduler_demo/controllers/evaluationController.js new file mode 100644 index 00000000..a4e5b0ab --- /dev/null +++ b/peer_eval_scheduler_demo/controllers/evaluationController.js @@ -0,0 +1,57 @@ +const EvaluationTask = require('../models/EvaluationTask'); +const Student = require('../models/Student'); + +// Sample controller to assign an initial paper +exports.assignPaper = async (req, res) => { + try { + const { submissionId, studentId, deadline } = req.body; + + const student = await Student.findOne({ studentId }); + if (!student) return res.status(404).json({ message: "Student not found." }); + + if (student.isRestricted) { + return res.status(400).json({ message: "Student is restricted from taking new assignments." }); + } + + const newTask = new EvaluationTask({ + submissionId, + assignedTo: student._id, + deadline + }); + + await newTask.save(); + + // Increment load + student.assignedEvaluationsCount += 1; + await student.save(); + + res.status(201).json({ message: "Paper successfully assigned.", task: newTask }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +// Sample controller to mark paper as completed +exports.completeEvaluation = async (req, res) => { + try { + const { taskId } = req.params; + + const task = await EvaluationTask.findById(taskId); + if (!task || task.status !== 'pending') { + return res.status(400).json({ message: "Invalid or already completed task." }); + } + + task.status = 'completed'; + await task.save(); + + // Decrease load, increase completed count + const student = await Student.findById(task.assignedTo); + student.assignedEvaluationsCount = Math.max(0, student.assignedEvaluationsCount - 1); + student.completedTasks += 1; + await student.save(); + + res.status(200).json({ message: "Evaluation marked as complete." }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; diff --git a/peer_eval_scheduler_demo/jobs/scheduler.js b/peer_eval_scheduler_demo/jobs/scheduler.js new file mode 100644 index 00000000..22b07d86 --- /dev/null +++ b/peer_eval_scheduler_demo/jobs/scheduler.js @@ -0,0 +1,55 @@ +const cron = require('node-cron'); +const EvaluationTask = require('../models/EvaluationTask'); +const Notification = require('../models/Notification'); +const evaluationService = require('../services/evaluationService'); + +const initScheduler = () => { + // Run this job every hour (adjust cron schedule based on needs) + cron.schedule('0 * * * *', async () => { + console.log('[Scheduler] Running deadline and reminder checks...'); + + try { + const now = new Date(); + + // --- 1. HANDLE DEADLINES (REASSIGNMENT) --- + // Find all pending tasks where the deadline has passed + const overdueTasks = await EvaluationTask.find({ + status: 'pending', + deadline: { $lt: now } + }); + + for (const task of overdueTasks) { + console.log(`[Scheduler] Initiating reassignment for Task: ${task._id}`); + await evaluationService.reassignTask(task._id); + } + + // --- 2. OPTIONAL ENHANCEMENT: SEND REMINDERS --- + // Find tasks due within the next 24 hours that haven't been reminded yet + const deadlineThreshold = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours from now + + const upcomingTasks = await EvaluationTask.find({ + status: 'pending', + deadline: { $gt: now, $lt: deadlineThreshold }, + reminderSent: false + }); + + for (const task of upcomingTasks) { + await Notification.create({ + userId: task.assignedTo, + message: "Reminder: You have an evaluation task due within 24 hours.", + type: "info" + }); + + task.reminderSent = true; + await task.save(); + } + + } catch (err) { + console.error('[Scheduler Error]', err); + } + }); + + console.log('[Scheduler] Initialized.'); +}; + +module.exports = initScheduler; diff --git a/peer_eval_scheduler_demo/models/EvaluationTask.js b/peer_eval_scheduler_demo/models/EvaluationTask.js new file mode 100644 index 00000000..5f1fc2f2 --- /dev/null +++ b/peer_eval_scheduler_demo/models/EvaluationTask.js @@ -0,0 +1,24 @@ +const mongoose = require('mongoose'); + +const evaluationTaskSchema = new mongoose.Schema({ + submissionId: { type: mongoose.Schema.Types.ObjectId, required: true }, // The paper to evaluate + assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'Student', required: true }, + + status: { + type: String, + enum: ['pending', 'completed', 'overdue'], + default: 'pending' + }, + + deadline: { type: Date, required: true }, + + // To avoid cycles / loops (so a paper isn't reassigned to the same person) + evaluatorsHistory: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Student' }], + + reassignmentCount: { type: Number, default: 0 }, + + // Optional flag to check if a reminder has been sent + reminderSent: { type: Boolean, default: false } +}, { timestamps: true }); + +module.exports = mongoose.model('EvaluationTask', evaluationTaskSchema); diff --git a/peer_eval_scheduler_demo/models/Notification.js b/peer_eval_scheduler_demo/models/Notification.js new file mode 100644 index 00000000..b5c43df9 --- /dev/null +++ b/peer_eval_scheduler_demo/models/Notification.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose'); + +const notificationSchema = new mongoose.Schema({ + userId: { type: mongoose.Schema.Types.ObjectId, ref: 'Student', required: true }, + message: { type: String, required: true }, + type: { + type: String, + enum: ['info', 'warning', 'escalation'], + default: 'info' + }, + read: { type: Boolean, default: false } +}, { timestamps: true }); + +module.exports = mongoose.model('Notification', notificationSchema); diff --git a/peer_eval_scheduler_demo/models/Student.js b/peer_eval_scheduler_demo/models/Student.js new file mode 100644 index 00000000..a5a1e08d --- /dev/null +++ b/peer_eval_scheduler_demo/models/Student.js @@ -0,0 +1,19 @@ +const mongoose = require('mongoose'); + +const studentSchema = new mongoose.Schema({ + studentId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + + // Tracking load for "Least Load First" + assignedEvaluationsCount: { type: Number, default: 0 }, + + // Accountability Trackers + completedTasks: { type: Number, default: 0 }, + missedDeadlines: { type: Number, default: 0 }, + warnings: { type: Number, default: 0 }, + + // High number of misses leads to restricted privileges + isRestricted: { type: Boolean, default: false } +}, { timestamps: true }); + +module.exports = mongoose.model('Student', studentSchema); diff --git a/peer_eval_scheduler_demo/services/evaluationService.js b/peer_eval_scheduler_demo/services/evaluationService.js new file mode 100644 index 00000000..b3c42523 --- /dev/null +++ b/peer_eval_scheduler_demo/services/evaluationService.js @@ -0,0 +1,132 @@ +const EvaluationTask = require('../models/EvaluationTask'); +const Student = require('../models/Student'); +const Notification = require('../models/Notification'); + +class EvaluationService { + + /** + * Reassigns an overdue paper to another student using the "Least Load First" strategy. + * Also applies accountability rules to the old student. + */ + async reassignTask(taskId) { + // 1. Fetch Task + const task = await EvaluationTask.findById(taskId).populate('assignedTo'); + if (!task || task.status !== 'pending') return; + + const currentStudent = task.assignedTo; + + // 2. Prevent infinite reassignment loops + if (task.reassignmentCount >= 3) { + await this.escalateToTA(task); + return; + } + + // 3. Accountability System (Penalize old student) + await this.applyAccountabilityPenalty(currentStudent); + + // 4. Update Task History (avoid assigning back to anyone who previously had it) + task.evaluatorsHistory.push(currentStudent._id); + + // 5. Auto Reassignment Logic (Least Load First) + const newAssignee = await this.findStudentForReassignment(task.evaluatorsHistory); + + if (!newAssignee) { + await this.escalateToTA(task, "No eligible student found for reassignment."); + return; + } + + // 6. Complete Reassignment + + // Decrement load of old student + await Student.findByIdAndUpdate(currentStudent._id, { $inc: { assignedEvaluationsCount: -1 } }); + + // Increment load of new student + await Student.findByIdAndUpdate(newAssignee._id, { $inc: { assignedEvaluationsCount: 1 } }); + + // Update the task data + task.assignedTo = newAssignee._id; + task.reassignmentCount += 1; + task.deadline = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000); // Give 3 more days + task.reminderSent = false; // Reset reminder for new student + + await task.save(); + + // 7. Notify new assignee + await Notification.create({ + userId: newAssignee._id, + message: "You have been assigned a new evaluation task due to a reassignment.", + type: "info" + }); + } + + /** + * Select a new student based on: + * 1. Not in evaluatorsHistory + * 2. Not restricted (too many missed deadlines) + * 3. Least assignedEvaluationsCount (Priority 1) + * 4. Lesser missedDeadlines (Priority 2) + */ + async findStudentForReassignment(evaluatorsHistory) { + const students = await Student.find({ + _id: { $nin: evaluatorsHistory }, + isRestricted: false + }) + .sort({ + assignedEvaluationsCount: 1, // Ascending (Least Load First) + missedDeadlines: 1 // Ascending (Prefer fewer missed deadlines) + }) + .limit(1); + + return students.length > 0 ? students[0] : null; + } + + /** + * Apply penalty rules when deadlines are missed + */ + async applyAccountabilityPenalty(student) { + student.missedDeadlines += 1; + + let warningMessage = ""; + let warningType = "warning"; + + if (student.missedDeadlines === 1) { + student.warnings += 1; + warningMessage = "Warning: You missed an evaluation deadline. Please be careful next time."; + } + else if (student.missedDeadlines === 2) { + student.warnings += 1; + warningMessage = "Strong Warning: You have missed 2 deadlines. Further misses will restrict your privileges."; + warningType = "escalation"; // Send a stronger warning + } + else if (student.missedDeadlines >= 3) { + student.isRestricted = true; // Restrict privileges + warningMessage = "Notice: You missed 3+ deadlines. Your privileges have been restricted and the teacher has been notified."; + warningType = "escalation"; + } + + await student.save(); + + if (warningMessage) { + await Notification.create({ + userId: student._id, + message: warningMessage, + type: warningType + }); + } + } + + /** + * If task cannot be reassigned anymore, escalate to TA/teacher. + */ + async escalateToTA(task, reason = "Maximum reassignment attempts reached.") { + task.status = 'overdue'; + await task.save(); + + // Here you would notify TA or Admin instead of a specific student + // e.g., Sending email to TA or inserting specialized notification DB record. + console.log(`[ESCALATION] Task ${task._id} escalated to TA. Reason: ${reason}`); + } + +} + +module.exports = new EvaluationService(); From 966af8d855fa7554708afd1a916d4303b5953f7e Mon Sep 17 00:00:00 2001 From: ritika-290 Date: Tue, 7 Apr 2026 17:39:12 +0530 Subject: [PATCH 02/25] checkout: temporary commit for worktree checkout From 363a460fc4e672f476e5f95e471f5cfe912a29f2 Mon Sep 17 00:00:00 2001 From: ritika-290 Date: Tue, 7 Apr 2026 17:39:24 +0530 Subject: [PATCH 03/25] checkout: temporary commit for worktree checkout From a2ef866db1dd03a196a330c26cb8ad67e5b5037d Mon Sep 17 00:00:00 2001 From: ritika-290 Date: Tue, 7 Apr 2026 17:50:25 +0530 Subject: [PATCH 04/25] Add server setup and UI testing --- peer_eval_scheduler_demo/.gitignore | 1 + peer_eval_scheduler_demo/app.js | 78 ++ peer_eval_scheduler_demo/package-lock.json | 1042 ++++++++++++++++++++ peer_eval_scheduler_demo/package.json | 18 + peer_eval_scheduler_demo/public/index.html | 91 ++ 5 files changed, 1230 insertions(+) create mode 100644 peer_eval_scheduler_demo/.gitignore create mode 100644 peer_eval_scheduler_demo/app.js create mode 100644 peer_eval_scheduler_demo/package-lock.json create mode 100644 peer_eval_scheduler_demo/package.json create mode 100644 peer_eval_scheduler_demo/public/index.html diff --git a/peer_eval_scheduler_demo/.gitignore b/peer_eval_scheduler_demo/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/peer_eval_scheduler_demo/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/peer_eval_scheduler_demo/app.js b/peer_eval_scheduler_demo/app.js new file mode 100644 index 00000000..2bc82f93 --- /dev/null +++ b/peer_eval_scheduler_demo/app.js @@ -0,0 +1,78 @@ +const express = require('express'); +const mongoose = require('mongoose'); +const path = require('path'); +const initScheduler = require('./jobs/scheduler'); +const evaluationController = require('./controllers/evaluationController'); +const evaluationService = require('./services/evaluationService'); +const Student = require('./models/Student'); +const EvaluationTask = require('./models/EvaluationTask'); +const Notification = require('./models/Notification'); + +const app = express(); +app.use(express.json()); +app.use(express.static(path.join(__dirname, 'public'))); + +// Set up MongoDB Connection +// Note: We use an in-memory or generic local DB for testing. Ensure MongoDB is running locally. +mongoose.connect('mongodb://127.0.0.1:27017/peereval_demo').then(async () => { + console.log('Connected to local MongoDB.'); + // Initialize scheduler + initScheduler(); +}).catch(err => { + console.error('Failed to connect to MongoDB', err); +}); + +// Seed Initial Data Route for testing +app.post('/api/seed', async (req, res) => { + try { + await Student.deleteMany({}); + await EvaluationTask.deleteMany({}); + await Notification.deleteMany({}); + + const s1 = await Student.create({ studentId: 'S001', name: 'Alice' }); + const s2 = await Student.create({ studentId: 'S002', name: 'Bob' }); + const s3 = await Student.create({ studentId: 'S003', name: 'Charlie' }); + + res.json({ message: 'Database seeded with Alice, Bob, Charlie.' }); + } catch (e) { + res.status(500).json({ error: e.message }); + } +}); + +// API Routes +app.post('/api/tasks', evaluationController.assignPaper); +app.put('/api/tasks/:taskId/complete', evaluationController.completeEvaluation); + +// Utility route to get all state for the dashboard +app.get('/api/state', async (req, res) => { + try { + const students = await Student.find() || []; + const tasks = await EvaluationTask.find().populate('assignedTo') || []; + const notifications = await Notification.find().populate('userId').sort({ createdAt: -1 }) || []; + res.json({ students, tasks, notifications }); + } catch (e) { + res.status(500).json({ error: e.message }); + } +}); + +// Utility Route to force cron behavior instantly +app.post('/api/test-cron', async (req, res) => { + const now = new Date(); + const overdueTasks = await EvaluationTask.find({ + status: 'pending', + deadline: { $lt: now } + }); + + let assigned = 0; + for (const task of overdueTasks) { + await evaluationService.reassignTask(task._id); + assigned++; + } + res.json({ message: `Cron tested! Processed ${assigned} overdue task(s).` }); +}); + +// Start Server +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`); +}); diff --git a/peer_eval_scheduler_demo/package-lock.json b/peer_eval_scheduler_demo/package-lock.json new file mode 100644 index 00000000..a869ad75 --- /dev/null +++ b/peer_eval_scheduler_demo/package-lock.json @@ -0,0 +1,1042 @@ +{ + "name": "peer_eval_scheduler_demo", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "peer_eval_scheduler_demo", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "express": "^5.2.1", + "mongoose": "^9.4.1", + "node-cron": "^4.2.1" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/kareem": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz", + "integrity": "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mongodb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", + "integrity": "sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.1.1", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongoose": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.4.1.tgz", + "integrity": "sha512-4rFBWa+/wdBQSfvnOPJBpiSG6UCEbhSQh865dEdaH9Y8WfHBUC+I2XT28dp0IBIGrEwmh+gzrgZgea5PbmrHWA==", + "license": "MIT", + "dependencies": { + "kareem": "3.2.0", + "mongodb": "~7.1", + "mpath": "0.9.0", + "mquery": "6.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz", + "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==", + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/peer_eval_scheduler_demo/package.json b/peer_eval_scheduler_demo/package.json new file mode 100644 index 00000000..acb47bd0 --- /dev/null +++ b/peer_eval_scheduler_demo/package.json @@ -0,0 +1,18 @@ +{ + "name": "peer_eval_scheduler_demo", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "express": "^5.2.1", + "mongoose": "^9.4.1", + "node-cron": "^4.2.1" + } +} diff --git a/peer_eval_scheduler_demo/public/index.html b/peer_eval_scheduler_demo/public/index.html new file mode 100644 index 00000000..9ac8e77c --- /dev/null +++ b/peer_eval_scheduler_demo/public/index.html @@ -0,0 +1,91 @@ + + + + + Peer Evaluation Reassignment Test Dashboard + + + +
+

Peer Evaluation System

+
+ + + + +
+ +
System Logs...
+ +
+
+

Students

+
Loading...
+
+
+

Tasks

+
Loading...
+
+
+

Notifications

+
Loading...
+
+
+
+ + + + From 061af95d716f34ec4784046d9aa8a1554152d34d Mon Sep 17 00:00:00 2001 From: ritika-290 Date: Tue, 7 Apr 2026 17:50:41 +0530 Subject: [PATCH 05/25] checkout: temporary commit for worktree checkout From fd85df09b7ef268ce8290f830efdd549436a9133 Mon Sep 17 00:00:00 2001 From: ritika-290 Date: Tue, 7 Apr 2026 21:06:30 +0530 Subject: [PATCH 06/25] Commit frontend UI improvements and React designs --- frontend_design_react_ideas.md | 240 ++++++++++++++++++++ peer_eval_scheduler_demo/public/index.html | 249 +++++++++++++++++---- 2 files changed, 444 insertions(+), 45 deletions(-) create mode 100644 frontend_design_react_ideas.md diff --git a/frontend_design_react_ideas.md b/frontend_design_react_ideas.md new file mode 100644 index 00000000..e034d61b --- /dev/null +++ b/frontend_design_react_ideas.md @@ -0,0 +1,240 @@ +# Peer Evaluation Frontend Design & Components + +Based on your backend logic (Auto-reassignment, accountability, notifications), here is a complete architectural design and code implementation for a highly interactive and student-friendly React frontend. + +--- + +## 1. Student Dashboard & 6. Accountability Visibility +The dashboard serves as the central hub. It clearly highlights urgent tasks with color coding and presents the user's accountability stats so they are aware of their standing. + +```jsx +import React, { useState, useEffect } from 'react'; +import TaskCard from './TaskCard'; +import AccountabilityStats from './AccountabilityStats'; + +const StudentDashboard = () => { + const [tasks, setTasks] = useState([]); + const [stats, setStats] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // Simulated API call + setTimeout(() => { + setTasks([ + { id: 'T001', submissionId: 'SUB-1042', deadline: new Date(Date.now() + 86400000), status: 'pending', reassigned: true }, + { id: 'T002', submissionId: 'SUB-2015', deadline: new Date(Date.now() + 400000), status: 'pending', reassigned: false } + ]); + setStats({ completedTasks: 4, missedDeadlines: 1, warnings: 1, isRestricted: false }); + setIsLoading(false); + }, 1000); + }, []); + + if (isLoading) return
Loading your dashboard...
; + + return ( +
+

My Dashboard

+ + {/* 6. Accountability Visibility */} + + +
+

Assigned Evaluations ({tasks.length})

+ {tasks.length === 0 ? ( +
🎉 You have no pending tasks!
+ ) : ( + tasks.map(task => ) + )} +
+
+ ); +}; +export default StudentDashboard; +``` + +--- + +## 2. Notifications UI & 4. Reassignment Awareness +A robust notification panel ensures students never miss a warning and are aware of the context behind their assigned papers (reassignment awareness). + +```jsx +import React, { useState } from 'react'; + +const NotificationPanel = ({ notifications }) => { + const [alerts, setAlerts] = useState(notifications); + + const markAsRead = (id) => { + setAlerts(alerts.map(a => a.id === id ? { ...a, read: true } : a)); + }; + + const getColor = (type) => { + switch(type) { + case 'info': return 'border-l-4 border-blue-500 bg-blue-50'; // đŸ”ĩ Info + case 'warning': return 'border-l-4 border-yellow-500 bg-yellow-50'; // 🟡 Warning + case 'escalation': return 'border-l-4 border-red-600 bg-red-50'; // 🔴 Escalation + default: return 'border-l-4 border-gray-400 bg-gray-50'; + } + }; + + return ( +
+

Notifications

+ {alerts.length === 0 &&

No new notifications.

} + + {alerts.map(alert => ( +
+
+

{alert.message}

+ {/* 4. Reassignment Awareness Tooltip included via messaging context */} + {alert.type === 'info' && alert.message.includes('reassignment') && ( + + â„šī¸ System reassigned task + + )} +
+ {!alert.read && } +
+ ))} +
+ ); +}; +export default NotificationPanel; +``` + +--- + +## 3. Evaluation Interface & Progress Indication +A clean UI for submission. It ensures validation stops premature submissions and tracks layout progress. + +```jsx +import React, { useState } from 'react'; + +const EvaluationForm = ({ task }) => { + const [answers, setAnswers] = useState({ q1: '', q2: '', q3: '' }); + const [step, setStep] = useState(1); + const totalSteps = 3; + + const handleNext = () => setStep(step + 1); + const handlePrev = () => setStep(step - 1); + + const isCurrentStepValid = () => { + if (step === 1) return answers.q1.length > 10; + if (step === 2) return answers.q2.length > 10; + return answers.q3.length > 10; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + if (!answers.q1 || !answers.q2 || !answers.q3) { + alert("âš ī¸ All fields are required."); // UX Feedback + return; + } + // API Call + alert("✅ Evaluation submitted successfully!"); + }; + + return ( +
+
+

Evaluating Submission: {task.submissionId}

+ {/* Progress Bar */} +
+
+
+

Question {step} of {totalSteps}

+
+ +
+ {step === 1 && ( +
+ +