-
Notifications
You must be signed in to change notification settings - Fork 0
Add participant management features to activities #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3f23aad
56b8909
63e0c30
15a900e
483e5e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| fastapi | ||
| uvicorn | ||
| pytest | ||
| httpx |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,11 +20,37 @@ document.addEventListener("DOMContentLoaded", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const spotsLeft = details.max_participants - details.participants.length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Build participants list | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let participantsList = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (details.participants.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| participantsList = ` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="participants-section"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <strong>Current Participants:</strong> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ul class="participants-list"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${details.participants.map(email => ` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <li> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span class="participant-email">${email}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span> | |
| <button type="button" class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant" aria-label="Remove participant ${email} from ${name}">🗑️</button> |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The participants list HTML interpolates email directly into both text and attribute contexts (data-email="...") via innerHTML. Since emails originate from user input, this enables HTML/attribute injection (XSS) if a crafted value contains quotes or markup. Build these nodes with document.createElement + textContent/dataset, or escape/sanitize values before inserting into the template string.
| // Build participants list | |
| let participantsList = ''; | |
| if (details.participants.length > 0) { | |
| participantsList = ` | |
| <div class="participants-section"> | |
| <strong>Current Participants:</strong> | |
| <ul class="participants-list"> | |
| ${details.participants.map(email => ` | |
| <li> | |
| <span class="participant-email">${email}</span> | |
| <span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span> | |
| </li> | |
| `).join('')} | |
| </ul> | |
| </div> | |
| `; | |
| } else { | |
| participantsList = ` | |
| <div class="participants-section"> | |
| <strong>Current Participants:</strong> | |
| <p class="no-participants">No participants yet. Be the first to sign up!</p> | |
| </div> | |
| `; | |
| } | |
| activityCard.innerHTML = ` | |
| <h4>${name}</h4> | |
| <p>${details.description}</p> | |
| <p><strong>Schedule:</strong> ${details.schedule}</p> | |
| <p><strong>Availability:</strong> ${spotsLeft} spots left</p> | |
| ${participantsList} | |
| `; | |
| // Build activity card content (static parts) | |
| activityCard.innerHTML = ` | |
| <h4>${name}</h4> | |
| <p>${details.description}</p> | |
| <p><strong>Schedule:</strong> ${details.schedule}</p> | |
| <p><strong>Availability:</strong> ${spotsLeft} spots left</p> | |
| `; | |
| // Build participants section using DOM APIs to avoid injecting untrusted HTML | |
| const participantsSection = document.createElement("div"); | |
| participantsSection.className = "participants-section"; | |
| const participantsTitle = document.createElement("strong"); | |
| participantsTitle.textContent = "Current Participants:"; | |
| participantsSection.appendChild(participantsTitle); | |
| if (details.participants.length > 0) { | |
| const participantsUl = document.createElement("ul"); | |
| participantsUl.className = "participants-list"; | |
| details.participants.forEach((email) => { | |
| const li = document.createElement("li"); | |
| const emailSpan = document.createElement("span"); | |
| emailSpan.className = "participant-email"; | |
| emailSpan.textContent = email; | |
| const deleteSpan = document.createElement("span"); | |
| deleteSpan.className = "delete-icon"; | |
| deleteSpan.dataset.activity = name; | |
| deleteSpan.dataset.email = email; | |
| deleteSpan.title = "Remove participant"; | |
| deleteSpan.textContent = "🗑️"; | |
| li.appendChild(emailSpan); | |
| li.appendChild(deleteSpan); | |
| participantsUl.appendChild(li); | |
| }); | |
| participantsSection.appendChild(participantsUl); | |
| } else { | |
| const noParticipantsMsg = document.createElement("p"); | |
| noParticipantsMsg.className = "no-participants"; | |
| noParticipantsMsg.textContent = "No participants yet. Be the first to sign up!"; | |
| participantsSection.appendChild(noParticipantsMsg); | |
| } | |
| activityCard.appendChild(participantsSection); |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchActivities() is now called after successful signup/unregister, but it appends <option>s to the activity <select> without clearing existing options. With the new refresh behavior, this will cause duplicate dropdown entries after each signup/delete. Consider resetting activitySelect (and preserving the selected value) before repopulating it.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Tests for Mergington High School API""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signup_for_activitydoesn’t enforcemax_participants. As a result, signups can exceed capacity even though the UI displays remaining spots, leading to inconsistent behavior and overbooking. Add a check before appending (e.g., iflen(participants) >= max_participantsthen return a 400/409 with an "Activity is full"-style detail).