diff --git a/controllers/auth/loginController.js b/controllers/auth/loginController.js index 03ed096..d2c0dea 100644 --- a/controllers/auth/loginController.js +++ b/controllers/auth/loginController.js @@ -12,6 +12,8 @@ const login = expressAsyncHandler(async (req, res, next) => { // const { email, password } = req.body; try { + + console.log("user ", req.user); // const user = await prisma.user.findUnique({ // where: { email } // }); diff --git a/controllers/registration/addRegistration.js b/controllers/registration/addRegistration.js index fde66ff..118c94a 100644 --- a/controllers/registration/addRegistration.js +++ b/controllers/registration/addRegistration.js @@ -1,5 +1,5 @@ const { PrismaClient, AccessTypes } = require("@prisma/client"); -const moment = require('moment-timezone'); +const moment = require("moment-timezone"); const prisma = new PrismaClient(); const { ApiError } = require("../../utils/error/ApiError"); const expressAsyncHandler = require("express-async-handler"); @@ -8,343 +8,414 @@ const { sendMail } = require("../../utils/email/nodeMailer"); const loadTemplate = require("../../utils/email/loadTemplate"); const uploadImage = require("../../utils/image/uploadImage"); -const validateCurrentForm = expressAsyncHandler(async (form, user, userSubmittedSections) => { +const validateCurrentForm = expressAsyncHandler( + async (form, user, userSubmittedSections) => { const { info, sections, formAnalytics } = form; const { eventMaxReg, isRegistrationClosed, isEventPast, isPublic } = info; - if (isRegistrationClosed === 'true' || isEventPast === 'true') { - throw new ApiError(400, "Sorry ! Registration has been closed for this event. If you feel this is an error, kindly contact us on fedkiit@gmail.com"); + if (isRegistrationClosed === "true" || isEventPast === "true") { + throw new ApiError( + 400, + "Sorry ! Registration has been closed for this event. If you feel this is an error, kindly contact us on fedkiit@gmail.com" + ); } if (!isPublic && req.user.access != AccessTypes.ADMIN) { - throw new ApiError(401, "Registering to a private form is not allowed. If you feel this is an error, kindly contact us on fedkiit@gmail.com"); + throw new ApiError( + 401, + "Registering to a private form is not allowed. If you feel this is an error, kindly contact us on fedkiit@gmail.com" + ); } - console.log(formAnalytics[0]?.regUserEmails) - console.log(user.regForm) - const isAlreadyRegistered = formAnalytics[0]?.regUserEmails.includes(user.email) || user.regForm.includes(form._id); + console.log(formAnalytics[0]?.regUserEmails); + console.log(user.regForm); + const isAlreadyRegistered = + formAnalytics[0]?.regUserEmails.includes(user.email) || + user.regForm.includes(form._id); if (isAlreadyRegistered) { - throw new ApiError(400, "User has already registered for this form. If you feel this is an error, kindly contact us on fedkiit@gmail.com"); + throw new ApiError( + 400, + "User has already registered for this form. If you feel this is an error, kindly contact us on fedkiit@gmail.com" + ); } - console.log("Form analytics ", formAnalytics[0]) - if ((formAnalytics[0]?.regUserEmails?.length || formAnalytics[0]?.totalRegistrationCount || 0) >= ((parseInt(eventMaxReg)) || 1)) { - console.log((formAnalytics[0]?.regUserEmails?.length || formAnalytics[0]?.totalRegistrationCount) >= (parseInt(eventMaxReg) || 1)); - console.log(eventMaxReg); - console.log(parseInt(eventMaxReg)) - throw new ApiError(400, "Maximum registration limit reached. If you feel this is an error, kindly contact us on fedkiit@gmail.com"); + console.log("Form analytics ", formAnalytics[0]); + if ( + (formAnalytics[0]?.regUserEmails?.length || + formAnalytics[0]?.totalRegistrationCount || + 0) >= (parseInt(eventMaxReg) || 1) + ) { + console.log( + (formAnalytics[0]?.regUserEmails?.length || + formAnalytics[0]?.totalRegistrationCount) >= + (parseInt(eventMaxReg) || 1) + ); + console.log(eventMaxReg); + console.log(parseInt(eventMaxReg)); + throw new ApiError( + 400, + "Maximum registration limit reached. If you feel this is an error, kindly contact us on fedkiit@gmail.com" + ); } -}); - + } +); const addRegistration = expressAsyncHandler(async (req, res, next) => { - console.log("Entering add", req.body); - console.log(req.body) - - const { _id } = req.body; - let sections = req.body.sections; - sections = JSON.parse(sections); - console.log("un-filtered sections", sections); - - // Filter out null values from sections - sections = sections.filter(section => section !== null); - console.log("filtered sections", sections); - - if (!_id || !sections || !Array.isArray(sections)) { - return next(new ApiError(400, "All fields are required")); + console.log("Entering add", req.body); + console.log("User Info", req.user); + console.log(req.body); + + const { _id } = req.body; + let sections = req.body.sections; + sections = JSON.parse(sections); + console.log("un-filtered sections", sections); + + // Filter out null values from sections + sections = sections.filter((section) => section !== null); + console.log("filtered sections", sections); + + if (!_id || !sections || !Array.isArray(sections)) { + return next(new ApiError(400, "All fields are required")); + } + + try { + const form = await prisma.form.findUnique({ + where: { id: _id }, + include: { formAnalytics: true }, + }); + console.log("Form fetched from the database", form); + + if (!form) { + return next(new ApiError(404, "Form not found")); } - try { - const form = await prisma.form.findUnique({ - where: { id: _id }, - include: { formAnalytics: true }, - }); - console.log("Form fetched from the database", form) + await validateCurrentForm(form, req.user, sections); + console.log("form validation passed"); + + const { info } = form; + const { relatedEvent } = info; + let teamName = [req.user.email.toUpperCase()]; + let teamCode = req.user.email; + let relatedEventForm = null; + let createTeamSection; + let joinTeamSection; + let teamExists; + let formTrackerTeamNameList = []; + + if (relatedEvent && relatedEvent !== "null" && relatedEvent !== null) { + relatedEventForm = await prisma.form.findUnique({ + where: { id: relatedEvent }, + include: { formAnalytics: true }, + }); + + if (!relatedEventForm) { + throw new ApiError(404, "Related Event not found"); + } + + // const userAlreadyRegisteredInRelatedForm = relatedEventForm.formAnalytics[0]?.regUserEmails?.includes(user.email); + // console.log(req.user.regForm.includes(form.relatedEvent)) + // console.log("related event regUserEmails", relatedEventForm.formAnalytics[0]?.regUserEmails) + + const userAlreadyRegisteredInRelatedForm = + req.user.regForm.includes(form.relatedEvent) || + relatedEventForm.formAnalytics[0]?.regUserEmails?.includes( + req.user.email + ); + + if (!userAlreadyRegisteredInRelatedForm) { + throw new ApiError( + 400, + `User must be registered in the related event : ${relatedEventForm.info.eventTitle}` + ); + } + console.log("related event check passed"); + } - if (!form) { - return next(new ApiError(404, "Form not found")); + let regTeamMemEmails = []; + // console.log("Team Name :", teamName) + // console.log("Team Code : ", teamCode) + // console.log("setions : ", sections); + + const sectionsObject = { + user_name: req.user.name, + user_id: req.user.id, + user_email: req.user.email, + date_time: moment().tz("Asia/Kolkata").format(), + amount: form?.info?.eventAmount || "0", + sections: sections, + }; + console.log("sections Object : ", sectionsObject); + console.log(relatedEventForm); + if (info.participationType !== "Individual") { + + + teamCode = await generateTeamCode( + relatedEventForm?.info.eventTitle, + info.eventTitle, + form.formAnalytics[0]?.regUserEmails.length + ); + + createTeamSection = sections.find( + (section) => section.name === "Create Team" + ); + joinTeamSection = !createTeamSection + ? sections.find((section) => section.name === "Join Team") + : null; + + if (createTeamSection) { + const teamNameField = createTeamSection.fields.find( + (field) => field.name === "Team Name" + ); + if (teamNameField) { + teamName = [teamNameField.value.toUpperCase().trim()]; + if (form.formAnalytics[0]?.regTeamNames.includes(teamName[0])) { + return next( + new ApiError( + 400, + "! This team name already taken !\n Please choose a different one." + ) + ); + } + regTeamMemEmails.push(req.user.email); + } else { + return next( + new ApiError(400, "Team Name field is required for Create Team") + ); } - - await validateCurrentForm(form, req.user, sections); - console.log('form validation passed'); - - const { info } = form; - const { relatedEvent } = info; - let teamName = [req.user.email.toUpperCase()]; - let teamCode = req.user.email; - let relatedEventForm = null; - let createTeamSection; - let joinTeamSection; - let teamExists; - let formTrackerTeamNameList = []; - - if (relatedEvent && relatedEvent !== "null" && relatedEvent !== null) { - relatedEventForm = await prisma.form.findUnique({ - where: { id: relatedEvent }, - include: { formAnalytics: true } - }); - - if (!relatedEventForm) { - throw new ApiError(404, "Related Event not found"); - } - - // const userAlreadyRegisteredInRelatedForm = relatedEventForm.formAnalytics[0]?.regUserEmails?.includes(user.email); - // console.log(req.user.regForm.includes(form.relatedEvent)) - // console.log("related event regUserEmails", relatedEventForm.formAnalytics[0]?.regUserEmails) - - const userAlreadyRegisteredInRelatedForm = req.user.regForm.includes(form.relatedEvent) || relatedEventForm.formAnalytics[0]?.regUserEmails?.includes(req.user.email); - - if (!userAlreadyRegisteredInRelatedForm) { - throw new ApiError(400, `User must be registered in the related event : ${relatedEventForm.info.eventTitle}`); - } - console.log("related event check passed"); + } else if (joinTeamSection) { + const teamCodeField = joinTeamSection.fields.find( + (field) => field.name === "Team Code" + ); + + if (teamCodeField) { + teamExists = await prisma.formRegistration.findUnique({ + where: { + formId_teamCode: { + formId: _id, + teamCode: teamCodeField.value, + }, + }, + }); + + if (!teamExists) { + console.log("Team does not exist"); + return next(new ApiError(404, "Invalid team code")); + } + + if ( + teamExists.regTeamMemEmails.length >= + (parseInt(info.maxTeamSize) || 1) + ) { + console.log("team full"); + return next(new ApiError(400, "This team is full")); + } + + // Log the teamExists object in a readable format + console.log("team Exists", JSON.stringify(teamExists, null, 2)); + + teamName = [teamExists.teamName]; + // console.log("team name array joining creating team", teamName) + // teamName = [...new Set([...teamName, ...(form.formAnalytics?.length > 0 ? form.formAnalytics[0].regTeamNames : [])])]; + console.log("team name before ", teamName); + console.log( + "existing team names", + form.formAnalytics?.length > 0 + ? form.formAnalytics[0].regTeamNames + : [] + ); + + // console.log("Team name array after joining team") + + teamCode = teamCodeField.value; + regTeamMemEmails = [...teamExists.regTeamMemEmails, req.user.email]; } - let regTeamMemEmails = []; - // console.log("Team Name :", teamName) - // console.log("Team Code : ", teamCode) - console.log("setions : ", sections); - - const sectionsObject = { - user_name: req.user.name, - user_id: req.user.id, - user_email: req.user.email, - date_time: moment().tz("Asia/Kolkata").format(), - amount: form?.info?.eventAmount || '0', - sections: sections - }; - console.log("sections Object : ", sectionsObject) - - if (info.participationType !== "Individual") { - - // console.log("related", relatedEventForm.info.eventTitle) - // console.log("eventTitle", info.eventTitle) - // console.log("count", form.formAnalytics[0]?.regUserEmails.length); - teamCode = await generateTeamCode(relatedEventForm.info.eventTitle, info.eventTitle, form.formAnalytics[0]?.regUserEmails.length); - - - createTeamSection = sections.find(section => section.name === "Create Team"); - joinTeamSection = !createTeamSection ? sections.find(section => section.name === "Join Team") : null; - - - if (createTeamSection) { - const teamNameField = createTeamSection.fields.find(field => field.name === "Team Name"); - if (teamNameField) { - teamName = [teamNameField.value.toUpperCase().trim()]; - if (form.formAnalytics[0]?.regTeamNames.includes(teamName[0])) { - return next(new ApiError(400, "! This team name already taken !\n Please choose a different one.")); - } - regTeamMemEmails.push(req.user.email); - } else { - return next(new ApiError(400, "Team Name field is required for Create Team")); - } - } else if (joinTeamSection) { - const teamCodeField = joinTeamSection.fields.find(field => field.name === "Team Code"); - - if (teamCodeField) { - teamExists = await prisma.formRegistration.findUnique({ - where: { - formId_teamCode: { - formId: _id, - teamCode: teamCodeField.value - } - }, - }); - - if (!teamExists) { - console.log("Team does not exist"); - return next(new ApiError(404, "Invalid team code")); - } - - if (teamExists.regTeamMemEmails.length >= (parseInt(info.maxTeamSize) || 1)) { - console.log("team full"); - return next(new ApiError(400, "This team is full")); - } - - // Log the teamExists object in a readable format - console.log("team Exists", JSON.stringify(teamExists, null, 2)); - + // sections.user_id = req.user.id; + // sections.user_email = req.user.email; + // sections.user_name = req.user.name; - teamName = [teamExists.teamName]; - // console.log("team name array joining creating team", teamName) - // teamName = [...new Set([...teamName, ...(form.formAnalytics?.length > 0 ? form.formAnalytics[0].regTeamNames : [])])]; - console.log("team name before ", teamName) - console.log("existing team names", form.formAnalytics?.length > 0 ? form.formAnalytics[0].regTeamNames : []); - - // console.log("Team name array after joining team") - - teamCode = teamCodeField.value; - regTeamMemEmails = [...teamExists.regTeamMemEmails, req.user.email]; - } - - - // sections.user_id = req.user.id; - // sections.user_email = req.user.email; - // sections.user_name = req.user.name; - - - // sectionsObject.push({ sections }); - } - - } + // sectionsObject.push({ sections }); + } + } - formTrackerTeamNameList = [...new Set([...teamName, ...(form.formAnalytics?.length > 0 ? form.formAnalytics[0].regTeamNames : [])])]; - console.log("set data ", formTrackerTeamNameList); - console.log("reg team members ", regTeamMemEmails) - - //const paymentSection = sections.find(section => section.name === "Payment Details"); - //const paymentSectionInActualForm = form.sections.find(section => section.name === "Payment Details") - // if (paymentSectionInActualForm && paymentSection) { - // console.log("payment section is present in the form"); - // if (req.files?.length > 0) { - // console.log("files", req.files); - // const imagePath = req.files[0].path; - // const result = await uploadImage(imagePath, req.files[0].fieldname || "PaymentScreenshot"); - // console.log(result); - // sectionsObject.transactionScreenShot = result.secure_url; - - // const paymentScreenshotField = paymentSection.fields.find(field => field.name === "Payment Screenshot" && field.type === "image"); - - // if (paymentScreenshotField) { - // // Update the value of the "Payment Screenshot" field with the secure URL - // paymentScreenshotField.value = result.secure_url; - // console.log("Payment Screenshot field updated successfully."); - // } else { - // console.error("Payment Screenshot field not found."); - // } - - // } - // else { - // return next(new ApiError(400, "Kindly Attach Payment Screenshot")); - // } - // } else if (paymentSectionInActualForm && !paymentSection) { - // return next(new ApiError(400, "Kindly fill the Payment section")); - // } - - console.log(sectionsObject) - - const transaction = await prisma.$transaction(async (prisma) => { - - // Step 1 : ADD REGISTRATION - const registration = await prisma.formRegistration.upsert({ - where: { - formId_teamCode: { - formId: _id, - teamCode - } - }, - update: { - value: { push: sectionsObject }, - regTeamMemEmails: { - set: regTeamMemEmails, - }, - teamSize: { - increment: 1 - } - }, - create: { - formId: _id, - userId: req.user.id, - value: [sectionsObject], - regTeamMemEmails: [req.user.email], - teamSize: 1, - teamCode, - teamName: teamName[0], - } - }); - - // const updatedUser = await updateUser({ email: req.user.email }, { regForm: _id }); - - // Step 2 : UPDATE THE USER - const updatedUser = await prisma.user.update({ - where: { - email: req.user.email - }, - data: { - regForm: { - push: _id - } - } - }); - - // Step 3 : UPDATE FORM ANALYTICS - const updateFormRegistrationList = await prisma.registrationTracker.upsert({ - where: { - formId: _id - }, - update: { - regUserEmails: { - push: req.user.email - }, - regTeamNames: { set: formTrackerTeamNameList }, - totalRegistrationCount: { - increment: 1 - } - }, - create: { - formId: _id, - regUserEmails: [req.user.email], - regTeamNames: { set: formTrackerTeamNameList }, - totalRegistrationCount: 1 - } - }); - - return { registration, updatedUser, updateFormRegistrationList }; + formTrackerTeamNameList = [ + ...new Set([ + ...teamName, + ...(form.formAnalytics?.length > 0 + ? form.formAnalytics[0].regTeamNames + : []), + ]), + ]; + console.log("set data ", formTrackerTeamNameList); + console.log("reg team members ", regTeamMemEmails); + + //const paymentSection = sections.find(section => section.name === "Payment Details"); + //const paymentSectionInActualForm = form.sections.find(section => section.name === "Payment Details") + // if (paymentSectionInActualForm && paymentSection) { + // console.log("payment section is present in the form"); + // if (req.files?.length > 0) { + // console.log("files", req.files); + // const imagePath = req.files[0].path; + // const result = await uploadImage(imagePath, req.files[0].fieldname || "PaymentScreenshot"); + // console.log(result); + // sectionsObject.transactionScreenShot = result.secure_url; + + // const paymentScreenshotField = paymentSection.fields.find(field => field.name === "Payment Screenshot" && field.type === "image"); + + // if (paymentScreenshotField) { + // // Update the value of the "Payment Screenshot" field with the secure URL + // paymentScreenshotField.value = result.secure_url; + // console.log("Payment Screenshot field updated successfully."); + // } else { + // console.error("Payment Screenshot field not found."); + // } + + // } + // else { + // return next(new ApiError(400, "Kindly Attach Payment Screenshot")); + // } + // } else if (paymentSectionInActualForm && !paymentSection) { + // return next(new ApiError(400, "Kindly fill the Payment section")); + // } + + console.log(sectionsObject); + + const transaction = await prisma.$transaction(async (prisma) => { + // Step 1 : ADD REGISTRATION + const registration = await prisma.formRegistration.upsert({ + where: { + formId_teamCode: { + formId: _id, + teamCode, + }, + }, + update: { + value: { push: sectionsObject }, + regTeamMemEmails: { + set: regTeamMemEmails, + }, + teamSize: { + increment: 1, + }, + }, + create: { + formId: _id, + userId: req.user.id, + value: [sectionsObject], + regTeamMemEmails: [req.user.email], + teamSize: 1, + teamCode, + teamName: teamName[0], + }, + }); + + + + // const updatedUser = await updateUser({ email: req.user.email }, { regForm: _id }); + // console.log("related", relatedEventForm.info.eventTitle) + // console.log("eventTitle", info.eventTitle) + // console.log("count", form.formAnalytics[0]?.regUserEmails.length); + teamCode = await generateTeamCode( + relatedEventForm?.info.eventTitle, + info.eventTitle, + form.formAnalytics[0]?.regUserEmails.length + ); + + // Step 3 : UPDATE FORM ANALYTICS + const updateFormRegistrationList = + await prisma.registrationTracker.upsert({ + where: { + formId: _id, + }, + update: { + regUserEmails: { + push: req.user.email, + }, + regTeamNames: { set: formTrackerTeamNameList }, + totalRegistrationCount: { + increment: 1, + }, + }, + create: { + formId: _id, + regUserEmails: [req.user.email], + regTeamNames: { set: formTrackerTeamNameList }, + totalRegistrationCount: 1, + }, }); - - console.log(transaction.updatedUser); - console.log("regTracker", transaction.updateFormRegistrationList); - - res.json({ message: form.info.successMessage || "Registration successful", teamName: transaction.registration.teamName, teamCode: transaction.registration.teamCode, user: transaction.updatedUser }); - // const placeholder = { - // name: req.user.email, - // successMessage: info.successMessage - // } - // const template = loadTemplate( - // (info.participationType === "Team") ? teamRegSuccess : indvRegSuccess, - // placeholder - // ) - - - const subject = `Successfully registered on ${info.eventTitle}`; - let template; - const placeholders = { - eventName: info.eventTitle ? info.eventTitle : "", - teamName: transaction.registration.teamName ? transaction.registration.teamName : "", - name: req.user.name ? req.user.name : "", - teamCode: transaction.registration.teamCode ? transaction.registration.teamCode : "", - successMessage: info.successMessage - } - - //take content form the team - if (info.participationType === "Team") { - - template = loadTemplate('teamEventRegistrationSuccess', placeholders); - } - else { - template = loadTemplate('individualEventRegistrationSuccess', placeholders); - - } - // const textContent = `Registration successfull in ${info.eventTitle}`; - sendMail(req.user.email, subject, template); - } - catch (error) { - console.error("Error during registration:", error); - next(new ApiError(error.stausCode || 500, error.message || "Error during registration process", error)); + return { registration,updateFormRegistrationList }; + }); + + console.log(transaction.updatedUser); + console.log("regTracker", transaction.updateFormRegistrationList); + + res.json({ + message: form.info.successMessage || "Registration successful", + teamName: transaction.registration.teamName, + teamCode: transaction.registration.teamCode, + user: transaction.updatedUser, + }); + // const placeholder = { + // name: req.user.email, + // successMessage: info.successMessage + // } + // const template = loadTemplate( + // (info.participationType === "Team") ? teamRegSuccess : indvRegSuccess, + // placeholder + // ) + + const subject = `Successfully registered on ${info.eventTitle}`; + let template; + const placeholders = { + eventName: info.eventTitle ? info.eventTitle : "", + teamName: transaction.registration.teamName + ? transaction.registration.teamName + : "", + name: req.user.name ? req.user.name : "", + teamCode: transaction.registration.teamCode + ? transaction.registration.teamCode + : "", + successMessage: info.successMessage, + }; + + //take content form the team + if (info.participationType === "Team") { + template = loadTemplate("teamEventRegistrationSuccess", placeholders); + } else { + template = loadTemplate( + "individualEventRegistrationSuccess", + placeholders + ); } - + // const textContent = `Registration successfull in ${info.eventTitle}`; + sendMail(req.user.email, subject, template); + } catch (error) { + console.error("Error during registration:", error); + next( + new ApiError( + error.stausCode || 500, + error.message || "Error during registration process", + error + ) + ); + } }); - -const generateTeamCode = async (relatedFormName, currentFormName, existingTeamsCount = 0) => { - const relatedEventCode = relatedFormName?.slice(0, 2).toUpperCase(); - const currentFormCode = currentFormName?.slice(0, 2).toUpperCase(); - const teamCount = existingTeamsCount.toString().padStart(3, '0'); - const randomNum = Math.floor(1000 + Math.random() * 9000).toString(); - - const teamCode = `${relatedEventCode ? relatedEventCode + "-" : ""}${currentFormCode ? currentFormCode + "-" : ""}${teamCount}-${randomNum}`; - return teamCode; +const generateTeamCode = async ( + relatedFormName, + currentFormName, + existingTeamsCount = 0 +) => { + const relatedEventCode = relatedFormName?.slice(0, 2).toUpperCase(); + const currentFormCode = currentFormName?.slice(0, 2).toUpperCase(); + const teamCount = existingTeamsCount.toString().padStart(3, "0"); + const randomNum = Math.floor(1000 + Math.random() * 9000).toString(); + + const teamCode = `${relatedEventCode ? relatedEventCode + "-" : ""}${ + currentFormCode ? currentFormCode + "-" : "" + }${teamCount}-${randomNum}`; + return teamCode; }; -module.exports = { addRegistration }; +module.exports = { addRegistration }; \ No newline at end of file diff --git a/controllers/registration/getTeamDetails.js b/controllers/registration/getTeamDetails.js index fdb86e5..561ed4d 100644 --- a/controllers/registration/getTeamDetails.js +++ b/controllers/registration/getTeamDetails.js @@ -8,11 +8,6 @@ const expressAsyncHandler = require("express-async-handler"); //@access Private (requires authentication) const getTeamDetails = expressAsyncHandler(async (req, res, next) => { try { - console.log("=== getTeamDetails called ==="); - console.log("Request params:", req.params); - console.log("Request user:", req.user); - console.log("User email:", req.user?.email); - const { formId } = req.params; const { email } = req.user; @@ -37,6 +32,10 @@ const getTeamDetails = expressAsyncHandler(async (req, res, next) => { } }); + if (!teamRegistration) { + return next(new ApiError(404, "No team registration found for this user in the specified form")); + } + const teamMembers = await prisma.user.findMany({ where: { email: { @@ -44,7 +43,6 @@ const getTeamDetails = expressAsyncHandler(async (req, res, next) => { } }, select: { - id: true, name: true, email: true, img: true, @@ -54,9 +52,6 @@ const getTeamDetails = expressAsyncHandler(async (req, res, next) => { } }); - - - // Check if this is a team event const isTeamEvent = teamRegistration.form.info.participationType === "Team"; diff --git a/controllers/registration/markAttendance.js b/controllers/registration/markAttendance.js new file mode 100644 index 0000000..0b0c81b --- /dev/null +++ b/controllers/registration/markAttendance.js @@ -0,0 +1,295 @@ +const { ApiError } = require("../../utils/error/ApiError"); +const { PrismaClient } = require("@prisma/client"); +const { getISTDateTime } = require("../../utils/datetime/getIST"); +const prisma = new PrismaClient(); +const jwt = require("jsonwebtoken"); +const ExcelJS = require("exceljs"); +const formatIST = require("../../utils/datetime/formatIST.js"); + +const getAttendanceCode = async (req, res, next) => { + try { + const { id: formId } = req.params; + const { teamCode } = req.query; + const { id: userId } = req.user; + + if (!formId || !userId) { + return next(new ApiError(400, "Form ID or User ID is missing.")); + } + + let formRegistrationDetails = null; + // Fetching event details from the database + if (teamCode) { + formRegistrationDetails = await prisma.formRegistration.findFirst({ + where: { + formId, + teamCode, + }, + }); + + console.log("formRegistrationDetails:", formRegistrationDetails); + } else { + const allFormRegistrationDetails = await prisma.formRegistration.findMany( + { + where: { + formId, + }, + } + ); + + console.log("allFormRegistrationDetails:", allFormRegistrationDetails); + + formRegistrationDetails = allFormRegistrationDetails.find( + (registration) => + registration.value.some((value) => value.user_id === userId) + ); + + console.log("Filtered formRegistrationDetails:", formRegistrationDetails); + + if (!formRegistrationDetails) { + return next( + new ApiError(404, "Form registration not found for the user.") + ); + } + formRegistrationDetails = { + ...formRegistrationDetails, + }; + } + + // If no registration details found, return an error + if (!formRegistrationDetails) { + return next(new ApiError(404, "Form registration not found.")); + } + + const info = formRegistrationDetails.value.find( + (value) => value.user_id === userId + ); + + // Put in attendance db + const attendanceData = { + formId: formId, + userId: userId, + teamName: formRegistrationDetails.teamName, + teamCode: formRegistrationDetails.teamCode, + info, + }; + + // Create or update attendance record + const attendanceDetails = await prisma.attendance.upsert({ + where: { + formId_userId_teamCode: { + formId: attendanceData.formId, + userId: attendanceData.userId, + teamCode: attendanceData.teamCode, + }, + }, + create: attendanceData, + update: attendanceData, + }); + + // Create JWT token for QR code that expires in 20 minutes + const token = jwt.sign( + { + attendanceToken: attendanceDetails.id, + }, + process.env.JWT_SECRET, + { expiresIn: "20m" } + ); + + // Return JWT token + return res.status(200).json({ + message: "Validation id generated successfully.", + attendanceToken: token, + }); + } catch (error) { + console.error("Error generating QR link:", error); + return next( + new ApiError( + error.statusCode || 500, + error.message || "Internal Server Error", + error + ) + ); + } +}; + +const markAttendance = async (req, res, next) => { + const { formId, token } = req.body; + + if (!token) { + return next(new ApiError(400, "Attendance token is required.")); + } + try { + // Verify the JWT token + let decoded; + try { + decoded = jwt.verify(token, process.env.JWT_SECRET); + } catch (err) { + return next(new ApiError(401, "Invalid or expired QR.")); + } + const attendanceId = decoded.attendanceToken; + + if (!attendanceId) { + return next(new ApiError(400, "Attendance ID is missing in the token.")); + } + + // Check if the attendance ID is valid + const attendanceRecord = await prisma.attendance.findUnique({ + where: { id: attendanceId }, + }); + + // If no attendance record found, return an error + if (!attendanceRecord) { + return next(new ApiError(404, "Attendance record not found.")); + } + + // Check if it belongs to the correct form + if (attendanceRecord.formId !== formId) { + return next( + new ApiError(400, "QR does not belong to the specified form.") + ); + } + + // Check if the attendance is already marked + if (attendanceRecord?.isPresent) { + return next(new ApiError(400, "Attendance already marked.")); + } + + // Check if payment is verified + // if (!attendanceRecord?.isPaymentVerified) { + // return next(new ApiError(400, "Payment not verified for this attendance.")); + // } + + // Mark attendance as present + const updatedAttendance = await prisma.attendance.update({ + where: { id: attendanceId }, + data: { isPresent: true, markedAt: getISTDateTime() }, + }); + + res.status(200).json({ + message: "Attendance marked successfully.", + attendance: updatedAttendance, + }); + } catch (error) { + console.error("Error marking attendance:", error); + return next( + new ApiError( + error.statusCode || 500, + error.message || "Internal Server Error", + error + ) + ); + } +}; + +const exportAttendance = async (req, res, next) => { + try { + const { id: formId } = req.params; + const { teamCode, format } = req.query; + + if (!formId) { + return next(new ApiError(400, "Form ID is required.")); + } + + const whereClause = { formId }; + if (teamCode) whereClause.teamCode = teamCode; + + const records = await prisma.attendance.findMany({ + where: whereClause, + }); + + if (!records.length) { + return next(new ApiError(404, "No attendance records found.")); + } + + // JSON output (unchanged) + if (!format || format === "json") { + let grouped; + if (teamCode) { + grouped = [{ teamCode, members: records }]; + } else { + grouped = Object.values( + records.reduce((acc, record) => { + if (!acc[record.teamCode]) { + acc[record.teamCode] = { teamCode: record.teamCode, members: [] }; + } + acc[record.teamCode].members.push(record); + return acc; + }, {}) + ); + } + return res.status(200).json(grouped); + } + + // XLSX output with styling + if (format === "xlsx") { + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet("Attendance"); + + // Define columns + sheet.columns = [ + { header: "User ID", key: "userId", width: 25 }, + { header: "Team Name", key: "teamName", width: 20 }, + { header: "Team Code", key: "teamCode", width: 15 }, + { header: "Present", key: "isPresent", width: 10 }, + { header: "Payment Verified", key: "isPaymentVerified", width: 20 }, + { header: "Phone Number", key: "phoneNumber", width: 15 }, + { header: "Marked At (IST)", key: "markedAtIST", width: 25 }, + ]; + + // Group by teamCode + const grouped = records.reduce((acc, r) => { + if (!acc[r.teamCode]) acc[r.teamCode] = []; + acc[r.teamCode].push(r); + return acc; + }, {}); + + Object.keys(grouped).forEach((teamCode) => { + grouped[teamCode].forEach((r) => { + const readableTime = r.markedAt ? formatIST(r.markedAt) : ""; + + const row = sheet.addRow({ + ...r, + phoneNumber: "1234", + markedAtIST: readableTime, + }); + + if (!r.isPresent) { + row.eachCell((cell) => { + cell.fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: "FFFFCCCC" }, // light red + }; + }); + } + }); + + // Add a blank row for separation + sheet.addRow({}); + }); + + // Write buffer + const buffer = await workbook.xlsx.writeBuffer(); + res.setHeader( + "Content-Disposition", + `attachment; filename="attendance_${formId}.xlsx"` + ); + res.setHeader( + "Content-Type", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ); + return res.send(buffer); + } + + return next(new ApiError(400, "Invalid format. Supported: json, xlsx.")); + } catch (error) { + console.error(error); + next(new ApiError(500, "Server error.")); + } +}; + +module.exports = { + getAttendanceCode, + markAttendance, + exportAttendance, +}; diff --git a/controllers/registration/registrationController.js b/controllers/registration/registrationController.js index 056b1a6..a7b64bc 100644 --- a/controllers/registration/registrationController.js +++ b/controllers/registration/registrationController.js @@ -1,9 +1,17 @@ -const { addRegistration } = require('./addRegistration'); -const { getRegistrationCount } = require('./countRegistration'); -const { downloadRegistration } = require('./downloadRegistration'); +const { addRegistration } = require("./addRegistration"); +const { getRegistrationCount } = require("./countRegistration"); +const { downloadRegistration } = require("./downloadRegistration"); +const { + getAttendanceCode, + markAttendance, + exportAttendance, +} = require("./markAttendance"); module.exports = { - addRegistration, - downloadRegistration, - getRegistrationCount + addRegistration, + downloadRegistration, + getRegistrationCount, + getAttendanceCode, + markAttendance, + exportAttendance, }; diff --git a/index.js b/index.js index eb1eb2e..98e99d7 100644 --- a/index.js +++ b/index.js @@ -113,9 +113,9 @@ app.use(express.urlencoded({ extended: true, limit: "16kb" })); app.use(cookieParser()); app.use(cors({ - origin: "*", - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - credentials: true, + origin: "*", + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + credentials: true, })); app.options('*', cors()); // handle preflight requests @@ -182,11 +182,11 @@ app.use(jsonParseErrorHandler); // Error-handling middleware - should be at the end app.use(errorHandler); -app.use('/keep-alive',(req,res) => { +app.use('/keep-alive', (req, res) => { res.sendStatus(200); // res.status(200).json({message : "Alive"}); }) -app.use('/',(req,res) => { +app.use('/', (req, res) => { res.sendStatus(404); }) // Start server diff --git a/middleware/access/checkAccess.js b/middleware/access/checkAccess.js index 0989576..7e354f7 100644 --- a/middleware/access/checkAccess.js +++ b/middleware/access/checkAccess.js @@ -1,60 +1,59 @@ -const { ApiError } = require('../../utils/error/ApiError'); +const { ApiError } = require("../../utils/error/ApiError"); const { PrismaClient, AccessTypes } = require("@prisma/client"); const prisma = new PrismaClient(); -const checkAccess = (...requiredAccess) => { - return async (req, res, next) => { - try { - - let user; - if (req.user) { - user = req.user; - } else { - const { email } = req.body; - - if (!email) { - throw new ApiError(400, "Email is required"); - } - - user = await prisma.user.findUnique({ - where: { email } - }); - - if (!user) { - throw new ApiError(404, "User not found!"); - } - - req.user = user; - } - - // Check if the user is admin - if (user.access === AccessTypes.ADMIN) { - return next(); - } - - // Check if user has any of the required access types - const hasRequiredAccess = requiredAccess.some(access => { - if (access === 'MEMBER') { - return user.access !== AccessTypes.USER; - } - return user.access === access; - }); - - if (hasRequiredAccess) { - return next(); - } - - throw new ApiError(403, 'Unauthorized'); - } catch (error) { - console.log("Could not pass checkAccess middleware",error); - if (error instanceof ApiError) { - next(error); - } else { - console.error("Unexpected error:", error); - next(new ApiError(500, "Internal Server Error", [], error)); - } +const checkAccess = (...requiredAccess) => { + return async (req, res, next) => { + try { + let user; + if (req.user) { + user = req.user; + } else { + const { email } = req.body; + + if (!email) { + throw new ApiError(400, "Email is required"); } - }; + + user = await prisma.user.findUnique({ + where: { email }, + }); + + if (!user) { + throw new ApiError(404, "User not found!"); + } + + req.user = user; + } + + // Check if the user is admin + if (user.access === AccessTypes.ADMIN) { + return next(); + } + + // Check if user has any of the required access types + const hasRequiredAccess = requiredAccess.some((access) => { + if (access === "MEMBER") { + return user.access !== AccessTypes.USER; + } + return user.access === access; + }); + + if (hasRequiredAccess) { + return next(); + } + + throw new ApiError(403, "Unauthorized"); + } catch (error) { + console.log("Could not pass checkAccess middleware", error); + if (error instanceof ApiError) { + next(error); + } else { + console.error("Unexpected error:", error); + next(new ApiError(500, "Internal Server Error", [], error)); + } + } + }; }; module.exports = { checkAccess }; diff --git a/package-lock.json b/package-lock.json index 85866d5..06b43cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -797,6 +797,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", "engines": { "node": ">=0.8" } @@ -1331,6 +1332,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", "dependencies": { "adler-32": "~1.3.0", "crc-32": "~1.2.0" @@ -1477,6 +1479,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", "engines": { "node": ">=0.8" } @@ -2206,6 +2209,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", @@ -2525,6 +2529,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", "engines": { "node": ">=0.8" } @@ -5296,6 +5301,7 @@ "version": "0.11.2", "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", "dependencies": { "frac": "~1.1.2" }, @@ -5703,6 +5709,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", "engines": { "node": ">=0.8" } @@ -5711,6 +5718,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", "engines": { "node": ">=0.8" } @@ -5759,6 +5767,7 @@ "version": "0.18.5", "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", diff --git a/prisma/schema/attendance.prisma b/prisma/schema/attendance.prisma new file mode 100644 index 0000000..7ec6aee --- /dev/null +++ b/prisma/schema/attendance.prisma @@ -0,0 +1,20 @@ +model attendance { + id String @id @default(auto()) @map("_id") @db.ObjectId + + userId String @db.ObjectId + + formId String @db.ObjectId + + teamName String + teamCode String + + isPresent Boolean @default(false) + isPaymentVerified Boolean @default(false) + + info Json + + markedAt DateTime? + + @@unique([formId, userId, teamCode]) + @@map("attendance") +} diff --git a/routes/api/forms/formRoutes.js b/routes/api/forms/formRoutes.js index cda814c..676b775 100644 --- a/routes/api/forms/formRoutes.js +++ b/routes/api/forms/formRoutes.js @@ -1,40 +1,64 @@ const express = require("express"); const router = express.Router(); -const formController = require('../../../controllers/forms/formController') -const registrationController = require('../../../controllers/registration/registrationController'); -const { getTeamDetails } = require('../../../controllers/registration/getTeamDetails'); -const { verifyToken } = require('../../../middleware/verifyToken'); -const { checkAccess } = require('../../../middleware/access/checkAccess'); -const multer = require('multer'); -const { imageUpload } = require('../../../middleware/upload'); +const formController = require("../../../controllers/forms/formController"); +const registrationController = require("../../../controllers/registration/registrationController"); +const { + getTeamDetails, +} = require("../../../controllers/registration/getTeamDetails"); +const { verifyToken } = require("../../../middleware/verifyToken"); +const { checkAccess } = require("../../../middleware/access/checkAccess"); +const multer = require("multer"); +const { imageUpload } = require("../../../middleware/upload"); const upload = multer(); // Add validations // Define your form routes here -router.get('/getAllForms', formController.getAllForms) -router.post('/contact', formController.contact); +router.get("/getAllForms", formController.getAllForms); +router.post("/contact", formController.contact); router.use(verifyToken); +router.get("/teamDetails/:formId", checkAccess("USER"), getTeamDetails); +router.use( + "/register", + checkAccess("USER"), + imageUpload.any(), + registrationController.addRegistration +); router.get( - "/teamDetails/:formId", - checkAccess('USER'), - getTeamDetails + "/export-attendance/:id", + checkAccess("ADMIN"), + registrationController.exportAttendance ); -router.use('/register', - checkAccess('USER'), - imageUpload.any(), - registrationController.addRegistration +router.use( + "/register", + checkAccess("USER"), + imageUpload.any(), + registrationController.addRegistration ); +router.get("/getFormAnalytics/:id", formController.analytics); router.get( - "/getFormAnalytics/:id", - formController.analytics -) + "/attendanceCode/:id", + checkAccess("USER"), + registrationController.getAttendanceCode +); + +router.post( + "/markAttendance", + // checkAccess([ + // "SENIOR_EXECUTIVE_TECHNICAL", + // "SENIOR_EXECUTIVE_CREATIVE", + // "SENIOR_EXECUTIVE_MARKETING", + // "SENIOR_EXECUTIVE_OPERATIONS", + // "SENIOR_EXECUTIVE_PR_AND_FINANCE", + // "SENIOR_EXECUTIVE_HUMAN_RESOURCE"]), + registrationController.markAttendance +); // router.get( // "/registrationCount", @@ -51,21 +75,21 @@ router.get( router.use(checkAccess("ADMIN")); router.post( - "/addForm", - imageUpload.fields([ - { name: "eventImg", maxCount: 1 }, - { name: "media", maxCount: 1 }, - ]), - formController.addForm + "/addForm", + imageUpload.fields([ + { name: "eventImg", maxCount: 1 }, + { name: "media", maxCount: 1 }, + ]), + formController.addForm ); router.delete("/deleteForm/:id", formController.deleteForm); router.put( - "/editForm/:id", - imageUpload.fields([ - { name: "eventImg", maxCount: 1 }, - { name: "media", maxCount: 1 }, - ]), - formController.editForm + "/editForm/:id", + imageUpload.fields([ + { name: "eventImg", maxCount: 1 }, + { name: "media", maxCount: 1 }, + ]), + formController.editForm ); router.get("/download/:id", registrationController.downloadRegistration); diff --git a/utils/datetime/formatIST.js b/utils/datetime/formatIST.js new file mode 100644 index 0000000..98559ce --- /dev/null +++ b/utils/datetime/formatIST.js @@ -0,0 +1,19 @@ +const formatIST = (date) => { + if (!date) return ""; + + const d = new Date(date); // will parse ISO string but may shift to UTC internally + + const iso = date.toISOString + ? date.toISOString() + : new Date(date).toISOString(); + + // Convert from the raw ISO string without letting JS adjust timezone + // Extract manually from the original IST-stored date + const [y, m, dayTime] = iso.split("-"); + const [day, time] = dayTime.split("T"); + const hhmm = time.substring(0, 5); // "HH:MM" + + return `${day}-${m}-${y} ${hhmm}`; +}; + +module.exports = formatIST; diff --git a/utils/datetime/getIST.js b/utils/datetime/getIST.js new file mode 100644 index 0000000..328af8b --- /dev/null +++ b/utils/datetime/getIST.js @@ -0,0 +1,6 @@ +function getISTDateTime(date = new Date()) { + const offset = 5.5 * 60 * 60 * 1000; + return new Date(date.getTime() + offset); +} + +module.exports = { getISTDateTime };