Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Getting Started with GitHub Copilot
# Getting Started with GitHub Copilot
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

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

Trailing whitespace detected at the end of line 1.

Suggested change
# Getting Started with GitHub Copilot
# Getting Started with GitHub Copilot

Copilot uses AI. Check for mistakes.

<img src="https://octodex.github.com/images/Professortocat_v2.png" align="right" height="200px" />

Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
fastapi
uvicorn
pytest
httpx
59 changes: 59 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,42 @@
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["[email protected]", "[email protected]"]
},
"Basketball Team": {
"description": "Competitive basketball practice and games",
"schedule": "Tuesdays and Thursdays, 4:00 PM - 6:00 PM",
"max_participants": 15,
"participants": ["[email protected]", "[email protected]"]
},
"Swimming Club": {
"description": "Swimming lessons and competitive training",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 25,
"participants": ["[email protected]", "[email protected]"]
},
"Art Club": {
"description": "Explore various art mediums including painting and sculpture",
"schedule": "Mondays, 3:30 PM - 5:00 PM",
"max_participants": 18,
"participants": ["[email protected]", "[email protected]"]
},
"Drama Club": {
"description": "Acting, theater production, and performance arts",
"schedule": "Thursdays, 3:30 PM - 5:30 PM",
"max_participants": 24,
"participants": ["[email protected]", "[email protected]"]
},
"Debate Team": {
"description": "Develop critical thinking and public speaking skills through debates",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 16,
"participants": ["[email protected]", "[email protected]"]
},
"Science Olympiad": {
"description": "Prepare for science competitions and conduct experiments",
"schedule": "Fridays, 3:30 PM - 5:30 PM",
"max_participants": 20,
"participants": ["[email protected]", "[email protected]"]
}
}

Expand All @@ -62,6 +98,29 @@ def signup_for_activity(activity_name: str, email: str):
# Get the specific activity
activity = activities[activity_name]

# Validate student is not already signed up
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up for this activity")

Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

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

The endpoints don't validate email format or check for maximum participants limit. While the signup endpoint should prevent exceeding max_participants, there's no validation to ensure the activity hasn't reached capacity before adding a new participant.

Consider adding validation:

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

Copilot uses AI. Check for mistakes.
# Add student
activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}


@app.delete("/activities/{activity_name}/unregister")
def unregister_from_activity(activity_name: str, email: str):
"""Unregister a student from an activity"""
# Validate activity exists
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")

# Get the specific activity
activity = activities[activity_name]

# Validate student is signed up
if email not in activity["participants"]:
raise HTTPException(status_code=400, detail="Student not signed up for this activity")

# Remove student
activity["participants"].remove(email)
return {"message": f"Unregistered {email} from {activity_name}"}
64 changes: 64 additions & 0 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,26 @@ document.addEventListener("DOMContentLoaded", () => {

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

const participantsList = details.participants.length > 0
? `<ul class="participants-list">
${details.participants.map(email => `
<li>
<span class="participant-email">${email}</span>
<button class="delete-btn" data-activity="${name}" data-email="${email}" title="Unregister">🗑️</button>
</li>
`).join('')}
</ul>`
: `<p class="no-participants">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-header"><strong>Participants:</strong></p>
${participantsList}
</div>
`;

Comment on lines +23 to 44
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

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

The email addresses and activity names are directly inserted into HTML without sanitization, which could lead to XSS vulnerabilities if malicious content is entered. Consider using textContent instead of template literals for user-provided data, or sanitize the input before rendering.

For example:

const li = document.createElement('li');
const emailSpan = document.createElement('span');
emailSpan.className = 'participant-email';
emailSpan.textContent = email; // Safe from XSS
const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-btn';
// ... set attributes safely
li.appendChild(emailSpan);
li.appendChild(deleteBtn);
Suggested change
const participantsList = details.participants.length > 0
? `<ul class="participants-list">
${details.participants.map(email => `
<li>
<span class="participant-email">${email}</span>
<button class="delete-btn" data-activity="${name}" data-email="${email}" title="Unregister">🗑️</button>
</li>
`).join('')}
</ul>`
: `<p class="no-participants">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-header"><strong>Participants:</strong></p>
${participantsList}
</div>
`;
// Title
const title = document.createElement("h4");
title.textContent = name;
activityCard.appendChild(title);
// Description
const desc = document.createElement("p");
desc.textContent = details.description;
activityCard.appendChild(desc);
// Schedule
const schedule = document.createElement("p");
const scheduleStrong = document.createElement("strong");
scheduleStrong.textContent = "Schedule:";
schedule.appendChild(scheduleStrong);
schedule.appendChild(document.createTextNode(" " + details.schedule));
activityCard.appendChild(schedule);
// Availability
const avail = document.createElement("p");
const availStrong = document.createElement("strong");
availStrong.textContent = "Availability:";
avail.appendChild(availStrong);
avail.appendChild(document.createTextNode(` ${spotsLeft} spots left`));
activityCard.appendChild(avail);
// Participants section
const participantsSection = document.createElement("div");
participantsSection.className = "participants-section";
const participantsHeader = document.createElement("p");
participantsHeader.className = "participants-header";
const headerStrong = document.createElement("strong");
headerStrong.textContent = "Participants:";
participantsHeader.appendChild(headerStrong);
participantsSection.appendChild(participantsHeader);
if (details.participants.length > 0) {
const ul = document.createElement("ul");
ul.className = "participants-list";
details.participants.forEach(email => {
const li = document.createElement("li");
const emailSpan = document.createElement("span");
emailSpan.className = "participant-email";
emailSpan.textContent = email;
li.appendChild(emailSpan);
const deleteBtn = document.createElement("button");
deleteBtn.className = "delete-btn";
deleteBtn.setAttribute("data-activity", name);
deleteBtn.setAttribute("data-email", email);
deleteBtn.setAttribute("title", "Unregister");
deleteBtn.textContent = "🗑️";
li.appendChild(deleteBtn);
ul.appendChild(li);
});
participantsSection.appendChild(ul);
} else {
const noParticipants = document.createElement("p");
noParticipants.className = "no-participants";
noParticipants.textContent = "No participants yet. Be the first to sign up!";
participantsSection.appendChild(noParticipants);
}
activityCard.appendChild(participantsSection);

Copilot uses AI. Check for mistakes.
Comment on lines +23 to 44
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

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

Activity name, description, and schedule are directly inserted into HTML without sanitization. While these values come from the backend, if the backend data is ever compromised or modified, this could lead to XSS vulnerabilities. Consider using DOM manipulation with textContent for better security:

const h4 = document.createElement('h4');
h4.textContent = name;
const descP = document.createElement('p');
descP.textContent = details.description;
// ... etc for other text content
activityCard.appendChild(h4);
activityCard.appendChild(descP);
Suggested change
const participantsList = details.participants.length > 0
? `<ul class="participants-list">
${details.participants.map(email => `
<li>
<span class="participant-email">${email}</span>
<button class="delete-btn" data-activity="${name}" data-email="${email}" title="Unregister">🗑️</button>
</li>
`).join('')}
</ul>`
: `<p class="no-participants">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-header"><strong>Participants:</strong></p>
${participantsList}
</div>
`;
// Build activity card safely using DOM methods
const h4 = document.createElement("h4");
h4.textContent = name;
activityCard.appendChild(h4);
const descP = document.createElement("p");
descP.textContent = details.description;
activityCard.appendChild(descP);
const scheduleP = document.createElement("p");
const scheduleStrong = document.createElement("strong");
scheduleStrong.textContent = "Schedule:";
scheduleP.appendChild(scheduleStrong);
scheduleP.appendChild(document.createTextNode(" " + details.schedule));
activityCard.appendChild(scheduleP);
const availP = document.createElement("p");
const availStrong = document.createElement("strong");
availStrong.textContent = "Availability:";
availP.appendChild(availStrong);
availP.appendChild(document.createTextNode(` ${spotsLeft} spots left`));
activityCard.appendChild(availP);
const participantsSection = document.createElement("div");
participantsSection.className = "participants-section";
const participantsHeader = document.createElement("p");
participantsHeader.className = "participants-header";
const participantsStrong = document.createElement("strong");
participantsStrong.textContent = "Participants:";
participantsHeader.appendChild(participantsStrong);
participantsSection.appendChild(participantsHeader);
if (details.participants.length > 0) {
const ul = document.createElement("ul");
ul.className = "participants-list";
details.participants.forEach(email => {
const li = document.createElement("li");
const emailSpan = document.createElement("span");
emailSpan.className = "participant-email";
emailSpan.textContent = email;
li.appendChild(emailSpan);
const deleteBtn = document.createElement("button");
deleteBtn.className = "delete-btn";
deleteBtn.setAttribute("data-activity", name);
deleteBtn.setAttribute("data-email", email);
deleteBtn.setAttribute("title", "Unregister");
deleteBtn.textContent = "🗑️";
li.appendChild(deleteBtn);
ul.appendChild(li);
});
participantsSection.appendChild(ul);
} else {
const noP = document.createElement("p");
noP.className = "no-participants";
noP.textContent = "No participants yet. Be the first to sign up!";
participantsSection.appendChild(noP);
}
activityCard.appendChild(participantsSection);

Copilot uses AI. Check for mistakes.
activitiesList.appendChild(activityCard);
Expand Down Expand Up @@ -59,6 +74,9 @@ document.addEventListener("DOMContentLoaded", () => {
const result = await response.json();

if (response.ok) {
// Refresh activities to show updated list
await fetchActivities();

messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
Expand All @@ -81,6 +99,52 @@ document.addEventListener("DOMContentLoaded", () => {
}
});

// Handle delete button clicks
activitiesList.addEventListener("click", async (event) => {
if (event.target.classList.contains("delete-btn")) {
const activityName = event.target.dataset.activity;
const email = event.target.dataset.email;

if (!confirm(`Are you sure you want to unregister ${email} from ${activityName}?`)) {
return;
}

try {
const response = await fetch(
`/activities/${encodeURIComponent(activityName)}/unregister?email=${encodeURIComponent(email)}`,
{
method: "DELETE",
}
);

const result = await response.json();

if (response.ok) {
// Refresh activities to show updated list
await fetchActivities();

messageDiv.textContent = result.message;
messageDiv.className = "success";
messageDiv.classList.remove("hidden");

// Hide message after 3 seconds
setTimeout(() => {
messageDiv.classList.add("hidden");
}, 3000);
} else {
messageDiv.textContent = result.detail || "Failed to unregister";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
}
} catch (error) {
messageDiv.textContent = "Failed to unregister. Please try again.";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
console.error("Error unregistering:", error);
}
}
});

// Initialize app
fetchActivities();
});
52 changes: 52 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,58 @@ section h3 {
margin-bottom: 8px;
}

.participants-section {
margin-top: 15px;
padding-top: 12px;
border-top: 1px solid #e0e0e0;
}

.participants-header {
margin-bottom: 8px;
color: #1a237e;
}

.participants-list {
list-style-type: none;
margin-left: 0;
color: #555;
}

.participants-list li {
margin-bottom: 5px;
padding: 8px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f0f0f0;
border-radius: 4px;
}

.participant-email {
flex-grow: 1;
}

.delete-btn {
background-color: transparent;
border: none;
cursor: pointer;
font-size: 18px;
padding: 4px 8px;
margin-left: 10px;
transition: transform 0.2s;
}

.delete-btn:hover {
transform: scale(1.2);
background-color: transparent;
}

Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

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

The delete button lacks proper keyboard accessibility. The button element should have a visible focus indicator for keyboard navigation. Consider adding a :focus or :focus-visible style to make the button more accessible.

For example:

.delete-btn:focus-visible {
  outline: 2px solid #1a237e;
  outline-offset: 2px;
}
Suggested change
.delete-btn:focus-visible {
outline: 2px solid #1a237e;
outline-offset: 2px;
}

Copilot uses AI. Check for mistakes.
.no-participants {
font-style: italic;
color: #999;
margin: 0;
}

.form-group {
margin-bottom: 15px;
}
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests package for the Mergington High School API"""
Loading
Loading