Skip to content

Improve student activity registration system#2

Merged
DUeffing merged 5 commits into
mainfrom
accelerate-with-copilot
Mar 31, 2026
Merged

Improve student activity registration system#2
DUeffing merged 5 commits into
mainfrom
accelerate-with-copilot

Conversation

@DUeffing

Copy link
Copy Markdown
Owner

This pull request adds major new features and improvements to the Mergington High School activities application. The main changes include expanding the list of available activities, implementing the ability for students to unregister from activities, enhancing the frontend to display and manage participants, and introducing comprehensive automated tests for the API endpoints.

Backend features and API improvements:

  • Added several new activities to the activities data structure in src/app.py, including Soccer Team, Basketball Team, Art Club, Drama Club, Debate Club, and Math Olympiad, each with their own schedule, participant list, and limits.
  • Implemented an endpoint to allow students to unregister from activities (/activities/{activity_name}/signup with DELETE), including validation and error handling. Also added validation to prevent duplicate signups.

Frontend enhancements:

  • Updated the frontend (src/static/app.js) to display the list of participants for each activity, including a "remove" button next to each participant.
  • Added frontend logic to handle participant removal via the new API endpoint, updating the UI and displaying success/error messages as appropriate.
  • Ensured the activities list refreshes after a signup or removal to keep the UI in sync with the backend.
  • Improved the styling of the participants list and remove button for clarity and usability in src/static/styles.css.

Testing:

  • Added a new test suite (tests/test_app.py) covering activity listing, signup (including duplicate and nonexistent activity cases), and unregistering (including error cases), ensuring robust API behavior.

@DUeffing
DUeffing requested a review from Copilot March 31, 2026 13:19
@DUeffing
DUeffing merged commit d469b4b into main Mar 31, 2026
5 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the Mergington High School activities app by adding more activities, introducing an unregister API endpoint, updating the frontend to display/manage participants, and adding API endpoint tests.

Changes:

  • Add new activities and add backend support for unregistering students (DELETE signup endpoint) plus duplicate-signup validation.
  • Enhance the frontend to render participants per activity and allow removing participants from the UI.
  • Introduce a pytest-based test suite covering core API behaviors.

Reviewed changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/app.py Adds more activities, prevents duplicate signup, and adds an unregister endpoint.
src/static/app.js Renders participants + remove buttons; wires up DELETE requests and refreshes activity data after changes.
src/static/styles.css Styles the new participants section and remove button.
tests/test_app.py Adds automated tests for listing, signup (success/duplicate/not found), and unregister flows.
requirements.txt Adds pytest to dependencies.
Comments suppressed due to low confidence (1)

src/static/app.js:52

  • fetchActivities() repopulates the <select id="activity"> by appending options but never clears existing options. Now that fetchActivities() is called after signup/removal, the dropdown will accumulate duplicate entries. Clear the select (preserving the placeholder option) before appending new options.
      activitiesList.innerHTML = "";

      // Populate activities list
      Object.entries(activities).forEach(([name, details]) => {
        const activityCard = document.createElement("div");
        activityCard.className = "activity-card";

        const spotsLeft = details.max_participants - details.participants.length;

        const participantsList = details.participants.length
          ? details.participants.map(email =>
              `<li>
                <span class="participant-email">${email}</span>
                <button class="remove-btn" data-activity="${name}" data-email="${email}" title="Remove participant">&#x2715;</button>
              </li>`
            ).join("")
          : "<li class='no-participants'>No participants yet</li>";

        activityCard.innerHTML = `
          <h4>${name}</h4>
          <p>${details.description}</p>
          <p><strong>Schedule:</strong> ${details.schedule}</p>
          <p><strong>Availability:</strong> ${spotsLeft} spots left</p>
          <div class="participants-section">
            <strong>Participants:</strong>
            <ul class="participants-list">
              ${participantsList}
            </ul>
          </div>
        `;

        activitiesList.appendChild(activityCard);

        // Add option to select dropdown
        const option = document.createElement("option");
        option.value = name;
        option.textContent = name;
        activitySelect.appendChild(option);
      });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app.py
# Validate student is not already signed up
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up")

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signup_for_activity appends the student without enforcing max_participants, so activities can be overbooked (and the frontend will show negative spots left). Add a capacity check before appending and return an appropriate 4xx error when the activity is full.

Suggested change
# Validate activity capacity
if len(activity["participants"]) >= activity["max_participants"]:
raise HTTPException(status_code=400, detail="Activity is full")

Copilot uses AI. Check for mistakes.
Comment thread src/static/app.js
Comment on lines +23 to 44
const participantsList = details.participants.length
? details.participants.map(email =>
`<li>
<span class="participant-email">${email}</span>
<button class="remove-btn" data-activity="${name}" data-email="${email}" title="Remove participant">&#x2715;</button>
</li>`
).join("")
: "<li class='no-participants'>No participants yet</li>";

activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
<div class="participants-section">
<strong>Participants:</strong>
<ul class="participants-list">
${participantsList}
</ul>
</div>
`;

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI is built via innerHTML with interpolated name, details.description, and especially email (user-controlled via signup). This allows HTML/attribute injection (XSS) if an email contains markup/quotes. Prefer creating DOM nodes and setting textContent, or sanitize/escape values before inserting into HTML.

Suggested change
const participantsList = details.participants.length
? details.participants.map(email =>
`<li>
<span class="participant-email">${email}</span>
<button class="remove-btn" data-activity="${name}" data-email="${email}" title="Remove participant">&#x2715;</button>
</li>`
).join("")
: "<li class='no-participants'>No participants yet</li>";
activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
<div class="participants-section">
<strong>Participants:</strong>
<ul class="participants-list">
${participantsList}
</ul>
</div>
`;
// Build activity card content safely using DOM APIs to avoid XSS
const titleEl = document.createElement("h4");
titleEl.textContent = name;
const descEl = document.createElement("p");
descEl.textContent = details.description;
const scheduleEl = document.createElement("p");
const scheduleStrong = document.createElement("strong");
scheduleStrong.textContent = "Schedule:";
scheduleEl.appendChild(scheduleStrong);
scheduleEl.appendChild(document.createTextNode(" " + details.schedule));
const availabilityEl = document.createElement("p");
const availabilityStrong = document.createElement("strong");
availabilityStrong.textContent = "Availability:";
availabilityEl.appendChild(availabilityStrong);
availabilityEl.appendChild(document.createTextNode(" " + spotsLeft + " spots left"));
const participantsSection = document.createElement("div");
participantsSection.className = "participants-section";
const participantsLabel = document.createElement("strong");
participantsLabel.textContent = "Participants:";
participantsSection.appendChild(participantsLabel);
const participantsUl = document.createElement("ul");
participantsUl.className = "participants-list";
if (details.participants.length) {
details.participants.forEach(email => {
const li = document.createElement("li");
const emailSpan = document.createElement("span");
emailSpan.className = "participant-email";
emailSpan.textContent = email;
const removeBtn = document.createElement("button");
removeBtn.className = "remove-btn";
removeBtn.dataset.activity = name;
removeBtn.dataset.email = email;
removeBtn.title = "Remove participant";
removeBtn.textContent = "✕";
li.appendChild(emailSpan);
li.appendChild(removeBtn);
participantsUl.appendChild(li);
});
} else {
const li = document.createElement("li");
li.className = "no-participants";
li.textContent = "No participants yet";
participantsUl.appendChild(li);
}
participantsSection.appendChild(participantsUl);
activityCard.appendChild(titleEl);
activityCard.appendChild(descEl);
activityCard.appendChild(scheduleEl);
activityCard.appendChild(availabilityEl);
activityCard.appendChild(participantsSection);

Copilot uses AI. Check for mistakes.
Comment thread src/static/app.js
? details.participants.map(email =>
`<li>
<span class="participant-email">${email}</span>
<button class="remove-btn" data-activity="${name}" data-email="${email}" title="Remove participant">&#x2715;</button>

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remove button is icon-only and relies on title for labeling, which is not a reliable accessible name. Add an aria-label (and/or visually hidden text) so screen readers announce what the button does (ideally including the participant email).

Suggested change
<button class="remove-btn" data-activity="${name}" data-email="${email}" title="Remove participant">&#x2715;</button>
<button class="remove-btn" data-activity="${name}" data-email="${email}" title="Remove participant" aria-label="Remove participant ${email}">&#x2715;</button>

Copilot uses AI. Check for mistakes.
Comment thread src/static/styles.css
background-color: #ffebee;
color: #b71c1c;
}

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.remove-btn has hover styling but no explicit focus styling. Add a :focus-visible (or :focus) style so keyboard users can see which remove button is focused.

Suggested change
.remove-btn:focus-visible,
.remove-btn:focus {
background-color: #ffebee;
color: #b71c1c;
outline: 2px solid #b71c1c;
outline-offset: 2px;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants