From 7d19fcf567e355b530abc17d21b6b72e21dad8f7 Mon Sep 17 00:00:00 2001 From: Subrahmanya Vaidya Date: Thu, 17 Apr 2025 21:52:20 +0530 Subject: [PATCH 1/2] winners-data-form --- package.json | 1 + src/app.js | 2 + src/controllers/teams.js | 92 +++++++++++++++++++++++++++++++++++ src/middlewares/winnerForm.js | 43 ++++++++++++++++ src/models/Winners.js | 67 +++++++++++++++++++++++++ src/routes/teams.js | 2 + 6 files changed, 207 insertions(+) create mode 100644 src/middlewares/winnerForm.js create mode 100644 src/models/Winners.js diff --git a/package.json b/package.json index a478889..7b9b1b9 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "moment": "^2.24.0", "mongoose": "^5.9.3", "morgan": "^1.9.1", + "multer": "^1.4.5-lts.2", "newrelic": "^9.15.0", "pm2": "^5.2.2", "twilio": "^4.10.0" diff --git a/src/app.js b/src/app.js index 4ecd9fb..e829e02 100644 --- a/src/app.js +++ b/src/app.js @@ -72,6 +72,7 @@ const notifications = require("./routes/notifications"); const settings = require("./routes/settings"); const slotting = require("./routes/slotting"); const feedback = require("./routes/feedback"); +const teams = require("./routes/teams"); app.use("/colleges", collegesRouter); app.use("/events", eventsRouter); @@ -90,6 +91,7 @@ app.use("/notifications", notifications); app.use("/settings", settings); app.use("/slotting", slotting); app.use("/feedback", feedback) +app.use("/teams", teams); // Error handlers app.use(handle404); diff --git a/src/controllers/teams.js b/src/controllers/teams.js index edd7a88..529610a 100644 --- a/src/controllers/teams.js +++ b/src/controllers/teams.js @@ -3,6 +3,9 @@ const TeamModel = require("../models/Team"); const EventModel = require("../models/Event"); const ParticipantModel = require("../models/Participant"); +const path = require("path"); +const fs = require("fs"); +const WinnerSubmission = require("../models/Winners"); /** * Add new team into the system. @@ -128,9 +131,98 @@ const deleteOne = async (req, res) => { } }; + +const getTeamByCollegeIdAndEventId = async (req, res) => { + try { + const { collegeId, eventId } = req.params; + if(!collegeId || !eventId){ + return res.status(400).json({ + status: 400, + message: "Bad Request", + }); + } + let team = await TeamModel.findOne({ event: eventId, college: collegeId }); + + return res.status(200).json({ + status: 200, + message: "Success", + data: team, + }); + } catch (e) { + return res.status(500).json({ + status: 500, + message: "Internal Server Error", + }); + } +}; + + + + +const VALID_PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/i; + +exports.submitWinners = async (req, res) => { + try { + const { collegeId, eventId, participants } = req.body; + + if (!collegeId || !eventId || !participants || !Array.isArray(participants)) { + return res.status(400).json({ message: "Invalid input data" }); + } + + const parsedParticipants = []; + + for (let i = 0; i < participants.length; i++) { + const p = participants[i]; + + // Validate PAN number + if (!VALID_PAN_REGEX.test(p.pan)) { + return res.status(400).json({ message: `Invalid PAN number for participant ${i + 1}` }); + } + + // Validate uploaded files exist + const panFile = req.files[`panPhoto-${i}`]?.[0]; + const chequeFile = req.files[`chequePhoto-${i}`]?.[0]; + + if (!panFile || !chequeFile) { + return res.status(400).json({ message: `Missing files for participant ${i + 1}` }); + } + + // File validation (size and type already handled in middleware) + parsedParticipants.push({ + name: p.name, + regNo: p.regNo, + pan: p.pan, + panPhotoPath: panFile.path, + accountNumber: p.accountNumber, + bankName: p.bankName, + branch: p.branch, + ifsc: p.ifsc, + phone: p.phone, + chequePhotoPath: chequeFile.path + }); + } + + // Save to DB + const newSubmission = new WinnerSubmission({ + collegeId, + eventId, + participants: parsedParticipants + }); + + await newSubmission.save(); + + res.status(200).json({ message: "Submission successful", submissionId: newSubmission._id }); + } catch (error) { + console.error("Winner submission error:", error); + res.status(500).json({ message: e.message }); + } +}; + + module.exports = { create, get, getAll, deleteOne, + getTeamByCollegeIdAndEventId }; diff --git a/src/middlewares/winnerForm.js b/src/middlewares/winnerForm.js new file mode 100644 index 0000000..a5bed6d --- /dev/null +++ b/src/middlewares/winnerForm.js @@ -0,0 +1,43 @@ +const multer = require('multer'); +const path = require('path'); + +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, 'uploads/'); + }, + + filename: function (req, file, cb) { + const ext = path.extname(file.originalname); + const index = file.fieldname.split('-')[1]; + const eventId = req.body.eventId; + const registrationNumber = req.body.participants[`registerNumber-${index}`]; + if (!eventId || !registrationNumber) { + return cb(new Error("Missing event ID or registration number")); + } + + const filename = `${eventId}-${registrationNumber}${ext}`; + cb(null, filename); + } +}); + +// File filter +const fileFilter = (req, file, cb) => { + const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; + + if (allowedTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('Only images and PDFs are allowed'), false); + } +}; + +// Upload config +const upload = multer({ + storage, + fileFilter, + limits: { + fileSize: 2 * 1024 * 1024 // 2 MB limit + } +}); + +module.exports = upload; diff --git a/src/models/Winners.js b/src/models/Winners.js new file mode 100644 index 0000000..d1eb1c1 --- /dev/null +++ b/src/models/Winners.js @@ -0,0 +1,67 @@ +const mongoose = require("mongoose"); + +const participantSchema = new mongoose.Schema({ + name: { + type: String, + required: true + }, + regNo: { + type: String, + required: true + }, + pan: { + type: String, + required: true + }, + panPhotoPath: { + type: String, + required: true + }, + accountNumber: { + type: String, + required: true + }, + bankName: { + type: String, + required: true + }, + branch: { + type: String, + required: true + }, + ifsc: { + type: String, + required: true + }, + phone: { + type: String, + required: true + }, + chequePhotoPath: { + type: String, + required: true + } +}, { _id: false }); + +const winnerFormData = new mongoose.Schema({ + collegeId: { + type: mongoose.Schema.Types.ObjectId, + ref: "College", + required: true + }, + eventId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Event", + required: true + }, + participants: { + type: [participantSchema], + required: true + }, + submittedAt: { + type: Date, + default: Date.now + } +}); + +module.exports = mongoose.model("WinnerSubmission", winnerFormData); diff --git a/src/routes/teams.js b/src/routes/teams.js index fa2e9fe..a25db2f 100644 --- a/src/routes/teams.js +++ b/src/routes/teams.js @@ -7,5 +7,7 @@ const Teams = require("../controllers/teams"); // Returns the list of all teams router.get("/", Teams.getAll); +router.get("/:collegeId/:eventId", Teams.getTeamByCollegeIdAndEventId); + module.exports = router; From bad293e1a1244f31edf6533c6628220cec15ca40 Mon Sep 17 00:00:00 2001 From: chandradiproy Date: Fri, 18 Apr 2025 01:16:51 +0530 Subject: [PATCH 2/2] winner form working finalized --- src/controllers/teams.js | 89 +++++++++------------- src/middlewares/winnerForm.js | 138 +++++++++++++++++++++++++++++----- src/routes/teams.js | 2 + 3 files changed, 157 insertions(+), 72 deletions(-) diff --git a/src/controllers/teams.js b/src/controllers/teams.js index 529610a..7270bf6 100644 --- a/src/controllers/teams.js +++ b/src/controllers/teams.js @@ -156,65 +156,45 @@ const getTeamByCollegeIdAndEventId = async (req, res) => { } }; - - - +// Regular expression for PAN validation (case-insensitive) const VALID_PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/i; - -exports.submitWinners = async (req, res) => { +const submitWinners = async (req, res) => { + console.log("Received files array:", JSON.stringify(req.files, null, 2)); try { - const { collegeId, eventId, participants } = req.body; - - if (!collegeId || !eventId || !participants || !Array.isArray(participants)) { - return res.status(400).json({ message: "Invalid input data" }); - } - - const parsedParticipants = []; - - for (let i = 0; i < participants.length; i++) { - const p = participants[i]; - - // Validate PAN number - if (!VALID_PAN_REGEX.test(p.pan)) { - return res.status(400).json({ message: `Invalid PAN number for participant ${i + 1}` }); - } - - // Validate uploaded files exist - const panFile = req.files[`panPhoto-${i}`]?.[0]; - const chequeFile = req.files[`chequePhoto-${i}`]?.[0]; - - if (!panFile || !chequeFile) { - return res.status(400).json({ message: `Missing files for participant ${i + 1}` }); - } - - // File validation (size and type already handled in middleware) - parsedParticipants.push({ - name: p.name, - regNo: p.regNo, - pan: p.pan, - panPhotoPath: panFile.path, - accountNumber: p.accountNumber, - bankName: p.bankName, - branch: p.branch, - ifsc: p.ifsc, - phone: p.phone, - chequePhotoPath: chequeFile.path + // Parse the participants string into an array + const participants = JSON.parse(req.body.participants); + + // Map frontend fields to schema fields + const processedParticipants = participants.map((p, i) => { + const panFile = req.files.find(file => file.fieldname === `panPhoto-${i}`); + const chequeFile = req.files.find(file => file.fieldname === `chequePhoto-${i}`); + + return { + name: p.name, + regNo: p.regNumber, + pan: p.panNumber, + accountNumber: p.bankAccount, + bankName: p.bankName, + branch: p.branch, + ifsc: p.ifsc, + phone: p.phone, + panPhotoPath: panFile ? panFile.path : null, + chequePhotoPath: chequeFile ? chequeFile.path : null + }; }); - } - // Save to DB - const newSubmission = new WinnerSubmission({ - collegeId, - eventId, - participants: parsedParticipants - }); - - await newSubmission.save(); + // Create and save the WinnerSubmission document + const winnerSubmission = new WinnerSubmission({ + collegeId: req.body.collegeId, + eventId: req.body.eventId, + participants: processedParticipants + }); - res.status(200).json({ message: "Submission successful", submissionId: newSubmission._id }); + await winnerSubmission.save(); + res.status(200).json({ message: 'Winners submitted successfully' }); } catch (error) { - console.error("Winner submission error:", error); - res.status(500).json({ message: e.message }); + console.error('Winner submission error:', error); + res.status(500).json({ error: 'Failed to submit winners' }); } }; @@ -224,5 +204,6 @@ module.exports = { get, getAll, deleteOne, - getTeamByCollegeIdAndEventId + submitWinners, + getTeamByCollegeIdAndEventId, }; diff --git a/src/middlewares/winnerForm.js b/src/middlewares/winnerForm.js index a5bed6d..ff8d2f3 100644 --- a/src/middlewares/winnerForm.js +++ b/src/middlewares/winnerForm.js @@ -1,43 +1,145 @@ -const multer = require('multer'); const path = require('path'); +const multer = require('multer'); +const fs = require('fs'); // Require fs for directory check +/** + * Multer disk storage configuration. + * Defines how uploaded files are stored. + */ const storage = multer.diskStorage({ + /** + * Sets the destination directory for uploaded files. + * @param {object} req - The request object. + * @param {object} file - The file object. + * @param {function} cb - The callback function. + */ destination: function (req, file, cb) { - cb(null, 'uploads/'); + const uploadPath = 'uploads/'; + // Ensure the upload directory exists + fs.mkdirSync(uploadPath, { recursive: true }); // Create directory if it doesn't exist + cb(null, uploadPath); }, + /** + * Generates a unique filename for the uploaded file. + * Filename format: eventId-registrationNumber-fieldname.ext (CHANGED) + * @param {object} req - The request object. + * @param {object} file - The file object. + * @param {function} cb - The callback function. + */ filename: function (req, file, cb) { - const ext = path.extname(file.originalname); - const index = file.fieldname.split('-')[1]; - const eventId = req.body.eventId; - const registrationNumber = req.body.participants[`registerNumber-${index}`]; - if (!eventId || !registrationNumber) { - return cb(new Error("Missing event ID or registration number")); - } + try { + // Get the file extension + const ext = path.extname(file.originalname); + // Extract the index from the fieldname (e.g., 'panPhoto-0' -> '0') + const fieldnameParts = file.fieldname.split('-'); + const index = fieldnameParts[fieldnameParts.length - 1]; // Get last part as index + + // Validate index + if (index === undefined || isNaN(parseInt(index))) { + return cb(new Error(`Invalid file fieldname format: ${file.fieldname}. Expected format like 'fieldName-index'.`)); + } + const participantIndex = parseInt(index); + + // Get eventId from the request body + const eventId = req.body.eventId; + + // --- Access Participants Data --- + // NOTE: Accessing req.body here can be fragile with upload.any() as + // body might not be fully parsed when this runs for the *first* file. + // It seems to work in your case, but consider alternatives if issues arise. + let participants; + try { + // Assume participants is sent as a JSON string + participants = JSON.parse(req.body.participants); + if (!Array.isArray(participants)) throw new Error("Not an array"); + } catch (parseError) { + console.error("Multer filename function failed to parse req.body.participants:", parseError); + // Cannot reliably get regNumber, use a fallback naming scheme + const fallbackFilename = `${Date.now()}-${file.fieldname}${ext}`; + console.warn(`Using fallback filename: ${fallbackFilename}`); + return cb(null, fallbackFilename); + // Or return an error: return cb(new Error("Could not parse participants data to generate filename")); + } + + // --- Input Validation --- + if (!eventId) { + // Use fallback if eventId is missing + const fallbackFilename = `${Date.now()}-${file.fieldname}${ext}`; + console.warn(`Missing eventId, using fallback filename: ${fallbackFilename}`); + return cb(null, fallbackFilename); + // return cb(new Error("Missing event ID in request body")); + } + + if (participantIndex >= participants.length || participantIndex < 0) { + return cb(new Error(`Participant index ${participantIndex} is out of bounds (Array length: ${participants.length})`)); + } - const filename = `${eventId}-${registrationNumber}${ext}`; - cb(null, filename); + // Get the specific participant object using the index + const participant = participants[participantIndex]; + + if (!participant || typeof participant !== 'object') { + return cb(new Error(`Invalid participant data found at index ${participantIndex}`)); + } + + // Get the registration number from the participant object + const registrationNumber = participant.regNumber; + + if (!registrationNumber) { + return cb(new Error(`Missing registration number for participant at index ${participantIndex}`)); + } + + // --- Construct the filename (ADDED fieldname for uniqueness) --- + // Format: eventId-regNumber-fieldname.ext + // Example: 67cb...-240970053-panPhoto-0.png + const uniqueFilename = `${eventId}-${registrationNumber}-${file.fieldname}${ext}`; + + console.log(`Generated filename for ${file.originalname}: ${uniqueFilename}`); // Log generated name + cb(null, uniqueFilename); + + } catch (error) { + // Catch any unexpected errors during filename generation + console.error("Unexpected error in multer filename function:", error); + cb(error); // Pass error to multer + } } }); -// File filter +/** + * File filter function to allow only specific file types. + * @param {object} req - The request object. + * @param {object} file - The file object. + * @param {function} cb - The callback function. + */ const fileFilter = (req, file, cb) => { + // Define allowed MIME types const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; + console.log(`Filtering file: ${file.originalname}, MIME type: ${file.mimetype}`); // Log file info + // Check if the file's MIME type is allowed if (allowedTypes.includes(file.mimetype)) { - cb(null, true); + console.log(`Allowing file: ${file.originalname}`); + cb(null, true); // Accept the file } else { - cb(new Error('Only images and PDFs are allowed'), false); + console.error(`Rejecting file (invalid type): ${file.originalname}`); // Log rejection + // Reject the file silently (won't appear in req.files, won't throw error) + cb(null, false); + // Alternatively, reject with an error: + // cb(new Error('Invalid file type. Only images (JPEG, PNG) and PDFs are allowed'), false); } }; -// Upload config +/** + * Multer upload configuration. + * Combines storage, file filter, and size limits. + */ const upload = multer({ - storage, - fileFilter, + storage: storage, // Use the defined disk storage + fileFilter: fileFilter, // Use the defined file filter limits: { - fileSize: 2 * 1024 * 1024 // 2 MB limit + fileSize: 2 * 1024 * 1024 // Set file size limit to 2 MB } }); +// Export the configured multer instance module.exports = upload; diff --git a/src/routes/teams.js b/src/routes/teams.js index a25db2f..4cac16a 100644 --- a/src/routes/teams.js +++ b/src/routes/teams.js @@ -4,10 +4,12 @@ const express = require("express"); const router = express.Router(); const Teams = require("../controllers/teams"); +const upload = require("../middlewares/winnerForm"); // Returns the list of all teams router.get("/", Teams.getAll); router.get("/:collegeId/:eventId", Teams.getTeamByCollegeIdAndEventId); +router.post("/submitWinnerForm", upload.any() ,Teams.submitWinners); module.exports = router;