Skip to content

Improve student activity registration system#2

Merged
mp182 merged 3 commits intomainfrom
accelerate-with-copilot
Mar 26, 2026
Merged

Improve student activity registration system#2
mp182 merged 3 commits intomainfrom
accelerate-with-copilot

Conversation

@mp182
Copy link
Copy Markdown
Owner

@mp182 mp182 commented Mar 26, 2026

This pull request adds participant management features for school activities, including the ability to register and unregister students via the API and user interface. It also introduces UI enhancements for displaying and managing participants, and adds comprehensive backend and validation tests to ensure correct behavior.

Backend enhancements:

  • Added several new activities to the activities data structure in src/app.py, each with descriptions, schedules, participant limits, and initial participants.
  • Implemented a DELETE endpoint /activities/{activity_name}/participants to allow unregistering a participant from an activity, with appropriate validation and error handling.

Frontend/UI improvements:

  • Updated src/static/app.js to display a list of participants for each activity, including a remove button for each participant, and to handle participant removal via the new API endpoint. Improved feedback messages and error handling for user actions. [1] [2]
  • Enhanced styles in src/static/styles.css to visually distinguish the participants section, participant list, and remove button for a better user experience.

Testing and validation:

  • Added backend tests in tests/test_endpoints.py to verify activity listing, successful signup, and successful participant removal.
  • Added validation tests in tests/test_validations.py to ensure proper error responses for duplicate signups, unknown activities, and attempts to remove non-participants.
  • Introduced a pytest fixture in tests/conftest.py to restore the activities state between tests, ensuring test isolation.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds participant-management capabilities to the activities app, including an API/UI flow to unregister students, richer participant display in the UI, and supporting automated tests.

Changes:

  • Added a DELETE /activities/{activity_name}/participants endpoint and duplicate-signup validation.
  • Updated the frontend to render participants per activity and allow removing them via the new endpoint, with refreshed messaging.
  • Introduced pytest-based endpoint/validation tests and a fixture to reset in-memory activity state between tests.

Reviewed changes

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

Show a summary per file
File Description
src/app.py Expands the in-memory activities list and adds/updates API logic for signup validation and participant removal.
src/static/app.js Renders participant lists with remove buttons; wires UI actions to the DELETE endpoint; centralizes user messaging.
src/static/styles.css Styles participant section/list and remove button to improve readability/UX.
tests/conftest.py Adds TestClient fixture and autouse state restore for test isolation.
tests/test_endpoints.py Adds endpoint-level tests for listing activities, signing up, and unregistering.
tests/test_validations.py Adds validation tests for unknown activities, duplicate signups, and invalid removals.
requirements.txt Adds pytest to support the new test suite.

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

Comment thread src/static/app.js
Comment on lines +36 to 65
const participantsMarkup = details.participants.length
? `<ul class="participant-list">${details.participants
.map(
(participant) =>
`<li class="participant-item">
<span class="participant-email">${participant}</span>
<button
type="button"
class="participant-remove"
data-activity="${name}"
data-email="${participant}"
aria-label="Remove ${participant} from ${name}"
title="Unregister participant"
>&times;</button>
</li>`
)
.join("")}</ul>`
: '<p class="participant-empty">No participants yet. Be the first to sign up.</p>';

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">
<p class="participants-title">Participants</p>
${participantsMarkup}
</div>
`;

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

activityCard.innerHTML interpolates server-provided fields (including details.participants, which contains user-supplied email values) directly into HTML. Because /signup accepts an arbitrary string for email, a malicious value can be persisted and rendered, resulting in DOM XSS. Build the card/participants list using DOM APIs (createElement + textContent) or apply proper escaping/sanitization before inserting into innerHTML/data-* attributes.

Suggested change
const participantsMarkup = details.participants.length
? `<ul class="participant-list">${details.participants
.map(
(participant) =>
`<li class="participant-item">
<span class="participant-email">${participant}</span>
<button
type="button"
class="participant-remove"
data-activity="${name}"
data-email="${participant}"
aria-label="Remove ${participant} from ${name}"
title="Unregister participant"
>&times;</button>
</li>`
)
.join("")}</ul>`
: '<p class="participant-empty">No participants yet. Be the first to sign up.</p>';
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">
<p class="participants-title">Participants</p>
${participantsMarkup}
</div>
`;
// Title
const titleEl = document.createElement("h4");
titleEl.textContent = name;
activityCard.appendChild(titleEl);
// Description
const descriptionEl = document.createElement("p");
descriptionEl.textContent = details.description;
activityCard.appendChild(descriptionEl);
// Schedule
const scheduleEl = document.createElement("p");
const scheduleStrong = document.createElement("strong");
scheduleStrong.textContent = "Schedule:";
scheduleEl.appendChild(scheduleStrong);
scheduleEl.appendChild(document.createTextNode(" " + details.schedule));
activityCard.appendChild(scheduleEl);
// Availability
const availabilityEl = document.createElement("p");
const availabilityStrong = document.createElement("strong");
availabilityStrong.textContent = "Availability:";
availabilityEl.appendChild(availabilityStrong);
availabilityEl.appendChild(
document.createTextNode(" " + spotsLeft + " spots left")
);
activityCard.appendChild(availabilityEl);
// Participants section
const participantsSection = document.createElement("div");
participantsSection.className = "participants-section";
const participantsTitle = document.createElement("p");
participantsTitle.className = "participants-title";
participantsTitle.textContent = "Participants";
participantsSection.appendChild(participantsTitle);
if (details.participants.length) {
const listEl = document.createElement("ul");
listEl.className = "participant-list";
details.participants.forEach((participant) => {
const itemEl = document.createElement("li");
itemEl.className = "participant-item";
const emailSpan = document.createElement("span");
emailSpan.className = "participant-email";
emailSpan.textContent = participant;
itemEl.appendChild(emailSpan);
const removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.className = "participant-remove";
removeButton.dataset.activity = name;
removeButton.dataset.email = participant;
removeButton.setAttribute(
"aria-label",
`Remove ${participant} from ${name}`
);
removeButton.title = "Unregister participant";
removeButton.textContent = "×";
itemEl.appendChild(removeButton);
listEl.appendChild(itemEl);
});
participantsSection.appendChild(listEl);
} else {
const emptyEl = document.createElement("p");
emptyEl.className = "participant-empty";
emptyEl.textContent =
"No participants yet. Be the first to sign up.";
participantsSection.appendChild(emptyEl);
}
activityCard.appendChild(participantsSection);

Copilot uses AI. Check for mistakes.
Comment thread src/static/app.js
Comment on lines 6 to +13

function showMessage(message, type) {
messageDiv.textContent = message;
messageDiv.className = type;
messageDiv.classList.remove("hidden");

setTimeout(() => {
messageDiv.classList.add("hidden");
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

showMessage schedules a new setTimeout each time it is called, but doesn’t cancel any prior timeout. If multiple messages are shown within 5 seconds, an earlier timeout can hide the newer message unexpectedly. Track the timeout id and clearTimeout it before scheduling a new hide.

Suggested change
function showMessage(message, type) {
messageDiv.textContent = message;
messageDiv.className = type;
messageDiv.classList.remove("hidden");
setTimeout(() => {
messageDiv.classList.add("hidden");
let messageHideTimeoutId = null;
function showMessage(message, type) {
// Cancel any previously scheduled hide to avoid hiding a newer message
if (messageHideTimeoutId !== null) {
clearTimeout(messageHideTimeoutId);
messageHideTimeoutId = null;
}
messageDiv.textContent = message;
messageDiv.className = type;
messageDiv.classList.remove("hidden");
messageHideTimeoutId = setTimeout(() => {
messageDiv.classList.add("hidden");
messageHideTimeoutId = null;

Copilot uses AI. Check for mistakes.
Comment thread src/static/app.js

function showMessage(message, type) {
messageDiv.textContent = message;
messageDiv.className = type;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

messageDiv.className = type replaces all classes on the message element. This makes it easy to accidentally drop baseline styling classes (e.g., a shared message class) or future utility classes. Prefer using classList to toggle success/error while preserving any existing base classes.

Suggested change
messageDiv.className = type;
// Remove any previous status classes while preserving baseline classes
messageDiv.classList.remove("success", "error");
if (type) {
messageDiv.classList.add(type);
}

Copilot uses AI. Check for mistakes.
Comment thread tests/conftest.py

@pytest.fixture()
def client():
return TestClient(app)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The client fixture returns a TestClient instance without closing it. Prefer a yield-style fixture (or with TestClient(app) as client: ...) so FastAPI lifespan startup/shutdown handlers run deterministically and resources/sockets are released between tests.

Suggested change
return TestClient(app)
with TestClient(app) as client:
yield client

Copilot uses AI. Check for mistakes.
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")

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

signup_for_activity adds the student without enforcing max_participants, so activities can be overbooked and the UI’s "spots left" can become negative/inaccurate. Add a capacity check before appending and return a 400 error when the activity is full.

Suggested change
# Validate activity is not over capacity (if a max is defined)
if "max_participants" in activity and len(activity["participants"]) >= activity["max_participants"]:
raise HTTPException(status_code=400, detail="Activity is full")

Copilot uses AI. Check for mistakes.
@mp182 mp182 merged commit 1ecb938 into main Mar 26, 2026
7 checks passed
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