Skip to content

Latest commit

 

History

166 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

University Event Management Application

As the title would suggest, this is an all in one event management system for a university. Right now its meant to serve the GUC but with very minimal changes it can serve any university or entity. Event managers (hereafter referred to as the Events Office) can post and manage events, while users can view details of and register for events on the system. Users can even pay for events they registered for using Stripe or the platform's wallet for convenience.

Motivation

While this project was ultimately created as the main requirement of the ACL course at the GUC and served as very powerful practical learning experience of the MongoDB, Express, React and Node stack (hereafter referred to as MERN); it actually is a viable project that we can see being very useful in the real world.

Build Status

On the more technical side, now that we are finalizing sprint 2, all features mandated by the course are now up and running (yes this implies our own features are in the pipeline - stay tuned!). Thankfully we have no system breaking bugs (none that we found during many rounds of testing anyway), however there are some quirks that should ideally be adressed in some future version. Namely some user actions require page refresh to take effect visually even though they take logical effect immediately. Also due to time limitations, unfortunately role based access control is enforced only on the frontend; ie someone who knows the hosting address of the backend may identify the endpoints and act maliciously. This is on our radar and the groundwork for its resolution is already in place.

Code Style

On a lighter note, at the start of sprint 1 we coordinated our code style to be consistent so it would be easier for us to debug and maintain each other’s implementations. Unfortunately however old habits die hard and we often default to our own styles after a while. That being said, in general any camel cased identifier is a function name while variables generally follow a lowercase style with underscores separating individual words.

Screenshots

Below are some sample screenshots from our project UI:

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Tech/Framework

As mentioned before, the MERN stack, a JavaScript technology suite powers this project.

  1. MongoDB A NoSQL, document-oriented database used to store application data in a JSON-like format.

  2. Express.js The backend framework running on Node.js, responsible for handling server-side logic, routing, and APIs.

  3. React A frontend JavaScript library used to build fast, interactive user interfaces with a great focus in reusability due to being component definition based.

  4. Node.js The JavaScript runtime environment that powers the backend and allows JavaScript to run on the server.

These work hand in hand to provide a seamless full-stack development experience using javascript as the only programming language across the entire application.

Features

Below is a comprehensive list of currently implemented system features:

  1. Students/Staff/TAs/Professors can sign up with my email, password, first name, last name and student/staff ID (Everyone should use their GUC email while signing up. Admins and Events Office do not sign up.)

  2. Vendors can sign up with an email, password, and company name

  3. Vendors can upload tax card and logo to prove company validity

  4. Students can receive a verification email after registration (The email contains a verification link that redirects to login. If not clicked, login should fail.)

  5. Admins can insert the correct role for Staff/TAs/Professors and send a verification email after approving their registration

  6. Staff/TAs/Professors can receive a verification email after admin verification (The link redirects to login. If not clicked, login should fail.)

  7. Admins can create other Admin/Events Office accounts with name, email, and password

  8. Admins can delete other Admin/Events Office accounts

  9. Students/Staff/Events Office/TAs/Professors/Admins can log in with my email and password (Everyone uses GUC email except companies and vendors.)

  10. Students/Staff/Events Office/TAs/Professors/Admins can log out successfully

  11. Students/Staff/Events Office/TAs/Professors/Admins can view all available events and their details including participating vendors (Events include workshops, trips, bazaars, booths, and conferences.)

  12. Students/Staff/Events Office/TAs/Professors/Admins can search events by name or type (Professor name / Event name)

  13. Students/Staff/Events Office/TAs/Professors/Admins can filter events by name, location, type, or date (Professor name in case of workshop or conference)

  14. Students/Staff/Events Office/TAs/Professors/Admins can sort events by date

  15. Students/Staff/TAs/Professors can rate an event that I attended

  16. Students/Staff/TAs/Professors can comment on an event that I attended

  17. Students/Staff/Events Office/TAs/Professors/Admins can view all ratings and comments on any event

  18. Admins can delete any inappropriate comments

  19. Admins can block any user

  20. Admins can view a list of all users along with their details and status (active/blocked)

  21. Students/Staff/Events Office/TAs/Professors can receive a warning email if my comment was deleted for being inappropriate

  22. Students/Staff/TAs/Professors can add an event to my favorites list

  23. Students/Staff/TAs/Professors can view my favorites list

  24. Students/Staff/TAs/Professors can register for a workshop/trip using name, email, and student/staff ID

  25. Students/Staff/TAs/Professors can pay for a workshop/trip using credit/debit card (Stripe) or wallet

  26. Students/Staff/TAs/Professors can receive a payment receipt via email

  27. Students/Staff/TAs/Professors can view a list of my registered events (upcoming and past)

  28. Students/Staff/TAs/Professors can cancel registration and receive refund to wallet (Only if at least 2 weeks before the event)

  29. Students/Staff/TAs/Professors can view refunded amount in my wallet

  30. Students/Staff/TAs/Professors can receive a certificate of attendance by email after finishing a workshop

  31. Events Office can create bazaars by adding name, dates, time, location, description, and registration deadline

  32. Events Office can edit bazaar details (Only if the bazaar hasn’t started yet)

  33. Events Office can create trips by adding name, location, price, dates, description, capacity, and deadline

  34. Events Office can edit trip details (Only if the trip start date hasn’t passed)

  35. Professors can create workshops with all required academic and logistical details

  36. Professors can edit workshop details

  37. Professors can view a list of all workshops that I created

  38. Professors can view participants and remaining spots for my workshops

  39. Events Office can receive system notifications when professors submit workshop requests

  40. Events Office can accept and publish workshops

  41. Events Office can reject workshops

  42. Events Office can request edits for workshop details

  43. Professors can view workshop status and requested edits

  44. Professors can receive system notifications if my workshop was accepted/rejected

  45. Events Office can create conferences with full details and website link (Website holds most information)

  46. Events Office can edit conference details

  47. Events Office can archive past events

  48. Events Office/Admins can delete events with no registrations

  49. Events Office can export registrant names to .xlsx (Except conferences)

  50. Events Office can restrict events to specific user types

  51. Events Office can generate QR codes for external visitors

  52. Events Office/Admins can view total attendee reports

  53. Events Office/Admins can filter attendee reports by name, type, or date

  54. Events Office/Admins can view sales and revenue reports

  55. Events Office/Admins can filter sales reports by type and/or date

  56. Events Office/Admins can sort sales reports by revenue

  57. Students/Staff/Events Office/TAs/Professors can receive notifications for new events

  58. Students/Staff/Events Office/TAs/Professors can receive reminders 1 day and 1 hour before registered events

  59. Vendors can view a list of upcoming bazaars

  60. Vendors can apply to join a bazaar with attendee names/emails and booth size (2x2 or 4x4; max 5 people)

  61. Vendors can apply for a platform booth with attendee info, duration, location, and size (Duration 1–4 weeks; size 2x2 or 4x4)

  62. Vendors can upload IDs of attending individuals

  63. Vendors can receive acceptance/rejection email for applications

  64. Vendors can pay participation fees (Deadline is 3 days after acceptance email)

  65. Vendors can receive a payment receipt via email

  66. Vendors can receive QR codes for all registered visitors by email

  67. Vendors can cancel participation request (Only if not paid yet)

  68. Vendors can view accepted upcoming bazaars/booths

  69. Vendors can view pending or rejected requests

  70. Vendors can apply to the GUC loyalty program (Includes discount rate, promo code, and terms)

  71. Vendors can cancel participation in loyalty program

  72. Students/Staff/TAs/Professors/Events Office/Admins can view loyalty program vendors with discounts and terms

  73. Students/Staff/TAs/Professors can receive notifications for new loyalty partners

  74. Events Office/Admins can receive notifications for pending vendor requests

  75. Events Office/Admins can view vendor participation request details

  76. Events Office/Admins can view/download uploaded documents

  77. Events Office/Admins can accept or reject vendor requests

  78. Students can view courts and availability

  79. Students can reserve courts (Reservation auto-includes name and GUC ID)

  80. Students/Staff/Events Office/TAs/Professors can view monthly gym schedules

  81. Students/Staff/TAs/Professors can register for gym sessions

  82. Events Office can create vendor polls for platform booths

  83. Students/Staff/TAs/Professors can vote in vendor polls

  84. Events Office can create gym sessions (date, time, duration, type, max participants)

  85. Events Office can cancel gym sessions

  86. Events Office can edit gym session (only date, time, and duration)

  87. Students/Staff/TAs/Professors can receive email if a gym session I registered for was cancelled or edited

Code Examples

This codebase is large so we thought we would provide some varried code examples of functions that we are particularly proud of:

Personal favorite of @DeveloSaurus

export async function refundEventPayment(req, res, next) {
  const {
    email,
    event_type,
    event_name,
    event_id,
    registration_price,
    start_date,
  } = req.body;
  const refund_deadline = new Date(start_date);
  const now = new Date();
  const millis_to_days = 86400000;
  if ((refund_deadline - now) / millis_to_days >= 14) {
    const transaction = await Transaction.create({
      owner: email,
      amount: registration_price,
      method: "Wallet",
      transaction_type: "Event Registration Refund",
      event_type: event_type,
      event_name: event_name,
      event_id: event_id,
      pending: false,
    });

    await Wallet.findOneAndUpdate(
      { owner: email },
      {
        $inc: { balance: registration_price },
        $push: { transaction_history: transaction },
      }
    );
    if (event_type === "Trip") {
      await Trip.findByIdAndUpdate(event_id, {
        $pull: { confirmed_participants: { email: email } },
      });
    } else {
      await workShop.findByIdAndUpdate(event_id, {
        $pull: { confirmed_participants: { email: email } },
      });
    }

    return res
      .status(200)
      .json({ message: "refund and unregistration successful" });
  }

  return res.status(409).json({ message: "refund deadline has passed" });
}

Personal favorite of @EchoKnights

export async function UploadCompanyVerificationDocs(req, res, next) {
  try {
    const vendorId = req.params.vendorId;
    if (!vendorId) {
      return res.status(400).json({ message: "vendorId is required" });
    }

    const taxCardFile = req.files?.tax_card?.[0] || null;
    const logoFile = req.files?.logo?.[0] || null;

    let company = await Company.findOne({ vendor_id: vendorId });

    if (!company) {
      if (!req.body?.name) {
        return res.status(400).json({ message: "Company name is required" });
      }
      company = await Company.create({
        vendor_id: vendorId,
        name: req.body.name,
      });
    }

    const update = {};
    if (req.body?.name) update.name = req.body.name;

    const metaBase = {
      ownerId: vendorId,
      relatedEntityType: "Company",
      relatedEntityId: company._id,
    };

    const [taxDoc, logoDoc] = await Promise.all([
      persistCompanyDoc(taxCardFile, {
        ...metaBase,
        category: "Company Tax Card",
      }),
      persistCompanyDoc(logoFile, { ...metaBase, category: "Company Logo" }),
    ]);

    if (taxDoc) update.tax_card_file = taxDoc._id;
    if (logoDoc) update.logo_file = logoDoc._id;

    if (Object.keys(update).length > 0) {
      company = await Company.findByIdAndUpdate(company._id, update, {
        new: true,
        runValidators: true,
      });
    }

    const populated = await Company.findById(company._id)
      .populate(["tax_card_file", "logo_file"])
      .lean();

    return res.status(200).json({
      company: populated,
      documents: {
        tax_card: formatFileResponse(populated.tax_card_file),
        logo: formatFileResponse(populated.logo_file),
      },
    });
  } catch (error) {
    next(error);
  }
}

Personal favorite of @AhmedShady911

export async function deleteComment(req, res, next) {
  try {
    const { event_id, type, comment_id, email } = req.body;

    // Pick the correct model
    let Model;
    switch (type) {
      case "Bazaar":
        Model = Bazaar;
        break;
      case "Conference":
        Model = conference;
        break;
      case "Trip":
        Model = Trip;
        break;
      case "Workshop":
        Model = workShop;
        break;
      case "Gym Session":
        Model = GymSession;
        break;
      case "Platform Booth":
        Model = PlatformApplication;
        break;
      default:
        return res.status(400).json({ message: "Invalid event type" });
    }

    // Fetch the event
    const event = await Model.findById(event_id);
    if (!event) {
      return res.status(404).json({ message: "Event not found" });
    }

    // Check if the comment exists
    const commentExists = event.comments?.some(
      (c) => c._id.toString() === comment_id
    );

    if (!commentExists) {
      return res.status(404).json({ message: "Comment not found in event" });
    }

    // Remove the comment
    await Model.findByIdAndUpdate(
      event_id,
      { $pull: { comments: { _id: comment_id } } },
      { new: true }
    );

    await sendDeleteCommentEmail(email);

    return res.status(200).json({ message: "Comment removed successfully" });
  } catch (err) {
    next(err);
  }
}

Personal favorite of @MostafaYakoutt

export async function bookCourt(req, res, next) {
  try {
    const courtId = req.params.id;
    // prefer authenticated user info, fallback to body
    const regId = req.user?.reg_id || req.body?.reg_id;
    const firstName = req.user?.first_name || req.body?.first_name || "";
    const lastName = req.user?.last_name || req.body?.last_name || "";
    const name = `${firstName} ${lastName}`.trim();

    if (!courtId)
      return res.status(400).json({ error: "Court id is required" });
    if (!regId)
      return res
        .status(400)
        .json({ error: "User GUC id (reg_id) is required to book a court" });

    // check existing court
    const court = await Court.findById(courtId);
    if (!court) return res.status(404).json({ error: "Court not found" });

    // if already booked by someone else, prevent double-booking
    // support legacy string value or new object format
    // determine if court is currently booked
    const bookedExists = (() => {
      if (!court.booked) return false; // null/undefined/empty string are falsy
      if (typeof court.booked === "object") return Boolean(court.booked.reg_id);
      if (typeof court.booked === "string") return court.booked.trim() !== "";
      return true;
    })();

    if (bookedExists) {
      // if booked using new object format and has reg_id, compare
      if (typeof court.booked === "object" && court.booked.reg_id) {
        if (String(court.booked.reg_id) !== String(regId)) {
          console.warn(
            `Book attempt by reg_id=${regId} failed; already booked by reg_id=${court.booked.reg_id}`
          );
          return res
            .status(409)
            .json({ error: "Court already booked", booked: court.booked });
        }
      } else {
        // legacy non-empty string value -> treat as booked
        console.warn(
          `Book attempt by reg_id=${regId} failed; already booked (legacy) by=${court.booked}`
        );
        return res
          .status(409)
          .json({ error: "Court already booked", booked: court.booked });
      }
    }

    // set booked to the user's name and reg_id
    const bookObj = { name, reg_id: regId, bookedAt: new Date() };
    const updated = await Court.findByIdAndUpdate(
      courtId,
      { booked: bookObj },
      { new: true }
    );
    return res
      .status(200)
      .json({ message: "Court booked successfully", court: updated });
  } catch (err) {
    next(err);
  }
}

Personal favorite of @omarhazem24

// Generate a QR code PNG for an event registration URL (for external visitors)
export async function generateEventQr(req, res, next) {
  try {
    const { type, id } = req.query || {};
    if (!type) return res.status(400).json({ error: "type is required" });
    //placeholder URL that the QR will encode
    const url = "https://www.guc.edu.eg/";

    const dataUrl = await QRCode.toDataURL(url, {
      errorCorrectionLevel: "H",
      type: "image/png",
      margin: 2,
    });
    // dataUrl is like 'data:image/png;base64,...'
    const base64 = dataUrl.split(",")[1];
    const imgBuf = Buffer.from(base64, "base64");
    res.setHeader("Content-Type", "image/png");
    res.setHeader(
      "Content-Disposition",
      `inline; filename="qr_${type}_${id || "event"}.png"`
    );
    return res.send(imgBuf);
  } catch (err) {
    console.error("Generate QR error:", err);
    return res
      .status(500)
      .json({ error: err.message || "Failed to generate QR code" });
  }
}

Installation

One of the best things about using node for this project is that installing all necessary dependencies and software is made trivial. Let's walk through it. Once you install node on your system, just clone this repository into the IDE of your choice and open up two terminals and navigate to the server directory in one terminal, and the client directory in the other. Now simply type and enter npm install into each terminal and this will install all necessary dependencies needed to run the project. Now you will also need to create 2 .env files to house all the important information that you do not want outsiders to access, this will be things such as your database connection URI and communication email app password. The first .env which should be placed in the server directory should be as follows:

MONGO_URI =
JWT_SECRET =
EMAIL =
EMAIL_PASSWORD =
VERIFYING_EMAIL =
VERIFYING_EMAIL_PASSWORD =
OFFICIAL_EMAIL =
OFFICIAL_EMAIL_PASSWORD =
STRIPE_SECRET_KEY =
STRIPE_WEBHOOK_SECRET =

the second .env which should be placed in the client directory is much simpler and should be as follows:

VITE_STRIPE_PUBLISHED_KEY =

Please note that the application can run without stripe functionality if the related variables are not set. To obtain the stripe api keys you need to sign up for a stripe account and copy them from the dashboard. The stripe webhook secret however is obtained in the terminal upon your first run.

Finally to run the project, in the server terminal type and enter npm run dev:hook to include a background command necessary for the stripe webhooks to function, or simply npm run dev if not using stripe. In the client terminal run npm run dev. Your application is now up and running! To shut it down simply type ctrl+c in both terminals.

API References

Below are some example routes for our backend API endpoints:

  1. app.post("/verify-account", sendEmail);

  2. app.get("/admin/getAllUsers", getAllUsers);

  3. app.get("/reports/sales", salesReport);

  4. app.get("/reports/qr", generateEventQr);

  5. app.post("/updateWorkShop/:id", updateWorkShop);

We also opted for dedicated routers to handle endpoints for specific controllers so here are some as well:

  1. app.use("/user-payments", userPaymentRouter);

  2. app.use("/trips", tripRouter);

  3. app.use("/user", userRouter);

Tests

Below are some screenshots from postman taken during our testing:

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Contribute

We are open to suggestions and new feature ideas, we would love to expand this project to work for an even wider range of entities that would like to streamline event management. Also while we are very happy with how the application looks right now, we are by no means graphic designers or UI experts and would welcome any and all feedback with open arms in this department.

Credits

During the development of this project we have consulted more sources than we care to count, but here are some of the more memorable ones:

  1. Stripe Integration Video

  2. Nodemailer Documentation

  3. Mongoose Documentation

License

Please refer to the Apache 2.0 License here:

License

Sincerely, The Five-Guys Team

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages