From 941f7b04b76fe4c3a804db8765a31a8cee710576 Mon Sep 17 00:00:00 2001
From: KC-dev <60586418+KCh-dev@users.noreply.github.com>
Date: Fri, 8 May 2026 14:42:26 +0000
Subject: [PATCH 1/3] Add extracurricular activities and signup validation to
the API
---
src/app.py | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/src/app.py b/src/app.py
index 4ebb1d9..61fd713 100644
--- a/src/app.py
+++ b/src/app.py
@@ -38,6 +38,42 @@
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
+ },
+ "Basketball Team": {
+ "description": "Practice and compete in basketball games",
+ "schedule": "Tuesdays and Thursdays, 4:00 PM - 6:00 PM",
+ "max_participants": 15,
+ "participants": []
+ },
+ "Soccer Club": {
+ "description": "Train and play soccer matches",
+ "schedule": "Mondays and Wednesdays, 3:00 PM - 5:00 PM",
+ "max_participants": 22,
+ "participants": []
+ },
+ "Art Club": {
+ "description": "Explore painting, drawing, and other visual arts",
+ "schedule": "Wednesdays, 3:30 PM - 5:00 PM",
+ "max_participants": 18,
+ "participants": []
+ },
+ "Drama Club": {
+ "description": "Act in plays and learn theater skills",
+ "schedule": "Thursdays, 4:00 PM - 6:00 PM",
+ "max_participants": 20,
+ "participants": []
+ },
+ "Debate Club": {
+ "description": "Develop argumentation and public speaking skills",
+ "schedule": "Mondays, 4:00 PM - 5:30 PM",
+ "max_participants": 16,
+ "participants": []
+ },
+ "Science Club": {
+ "description": "Conduct experiments and learn about scientific concepts",
+ "schedule": "Fridays, 2:00 PM - 4:00 PM",
+ "max_participants": 25,
+ "participants": []
}
}
@@ -62,6 +98,10 @@ def signup_for_activity(activity_name: str, email: str):
# Get the specific activity
activity = activities[activity_name]
+ # validate the studen is already signed in
+ if email in activity["participants"]:
+ raise HTTPException(status_code=400, detail="Student already signed up for this activity")
+
# Add student
activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}
From 5a42953080e76b97cc64846cde1481e15c52fc88 Mon Sep 17 00:00:00 2001
From: KC-dev <60586418+KCh-dev@users.noreply.github.com>
Date: Fri, 8 May 2026 14:48:21 +0000
Subject: [PATCH 2/3] Enhance activity display with participant management
features and styling
---
src/static/app.js | 47 ++++++++++++++++++++++++++++++++++++++
src/static/styles.css | 53 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 100 insertions(+)
diff --git a/src/static/app.js b/src/static/app.js
index dcc1e38..f588e29 100644
--- a/src/static/app.js
+++ b/src/static/app.js
@@ -13,22 +13,67 @@ document.addEventListener("DOMContentLoaded", () => {
// Clear loading message
activitiesList.innerHTML = "";
+ // Clear activity dropdown options (except the first one)
+ activitySelect.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 > 0
+ ? details.participants.map(p => `
+
+ ${p}
+
+
+ `).join('')
+ : 'No participants yet';
activityCard.innerHTML = `
${name}
${details.description}
Schedule: ${details.schedule}
Availability: ${spotsLeft} spots left
+
`;
activitiesList.appendChild(activityCard);
+ // Add event listeners for delete buttons
+ const deleteButtons = activityCard.querySelectorAll('.delete-participant');
+ deleteButtons.forEach(button => {
+ button.addEventListener('click', async (event) => {
+ event.preventDefault();
+ const activity = button.getAttribute('data-activity');
+ const email = button.getAttribute('data-email');
+
+ try {
+ const response = await fetch(
+ `/activities/${encodeURIComponent(activity)}/unregister?email=${encodeURIComponent(email)}`,
+ {
+ method: 'POST',
+ }
+ );
+
+ if (response.ok) {
+ // Refresh activities
+ fetchActivities();
+ } else {
+ console.error('Failed to unregister participant');
+ }
+ } catch (error) {
+ console.error('Error unregistering participant:', error);
+ }
+ });
+ });
+
// Add option to select dropdown
const option = document.createElement("option");
option.value = name;
@@ -62,6 +107,8 @@ document.addEventListener("DOMContentLoaded", () => {
messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
+ // Refresh activities to show the new participant
+ fetchActivities();
} else {
messageDiv.textContent = result.detail || "An error occurred";
messageDiv.className = "error";
diff --git a/src/static/styles.css b/src/static/styles.css
index a533b32..6abf539 100644
--- a/src/static/styles.css
+++ b/src/static/styles.css
@@ -74,6 +74,59 @@ section h3 {
margin-bottom: 8px;
}
+.participants-section {
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.participants-section strong {
+ display: block;
+ margin-bottom: 10px;
+ color: #1a237e;
+}
+
+.participants-list {
+ list-style: none;
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.participant-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ padding: 5px 0;
+}
+
+.participant-name {
+ color: #555;
+ flex: 1;
+}
+
+.delete-participant {
+ background: none;
+ border: none;
+ color: #d32f2f;
+ font-size: 20px;
+ cursor: pointer;
+ padding: 0 8px;
+ transition: color 0.2s, transform 0.2s;
+ line-height: 1;
+}
+
+.delete-participant:hover {
+ color: #b71c1c;
+ transform: scale(1.2);
+}
+
+.participants-list-empty {
+ color: #999;
+ font-style: italic;
+ margin-bottom: 5px;
+}
+
.form-group {
margin-bottom: 15px;
}
From 95c79d4a6a141e859695538a9dc0bbc77dddd1c5 Mon Sep 17 00:00:00 2001
From: KC-dev <60586418+KCh-dev@users.noreply.github.com>
Date: Fri, 8 May 2026 14:54:00 +0000
Subject: [PATCH 3/3] Add tests for activity signup functionality and response
handling
---
requirements.txt | 3 ++-
tests/test_app.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 65 insertions(+), 1 deletion(-)
create mode 100644 tests/test_app.py
diff --git a/requirements.txt b/requirements.txt
index 5d9efb5..f2821b2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
fastapi
uvicorn
httpx
-watchfiles
\ No newline at end of file
+watchfiles
+pytest
\ No newline at end of file
diff --git a/tests/test_app.py b/tests/test_app.py
new file mode 100644
index 0000000..4df666f
--- /dev/null
+++ b/tests/test_app.py
@@ -0,0 +1,63 @@
+import copy
+
+from fastapi.testclient import TestClient
+
+from src.app import activities, app
+
+client = TestClient(app)
+original_activities = copy.deepcopy(activities)
+
+
+def reset_activities():
+ activities.clear()
+ activities.update(copy.deepcopy(original_activities))
+
+
+def test_root_redirects_to_static_index_html():
+ response = client.get("/", follow_redirects=False)
+
+ assert response.status_code == 307
+ assert response.headers["location"] == "/static/index.html"
+
+
+def test_get_activities_returns_all_activities():
+ reset_activities()
+
+ response = client.get("/activities")
+
+ assert response.status_code == 200
+ assert response.json() == original_activities
+
+
+def test_signup_for_activity_adds_participant():
+ reset_activities()
+
+ activity_name = "Basketball Team"
+ email = "alex@mergington.edu"
+
+ response = client.post(f"/activities/{activity_name}/signup", params={"email": email})
+
+ assert response.status_code == 200
+ assert response.json() == {"message": f"Signed up {email} for {activity_name}"}
+ assert email in activities[activity_name]["participants"]
+
+
+def test_signup_missing_activity_returns_404():
+ reset_activities()
+
+ response = client.post("/activities/Unknown Club/signup", params={"email": "student@mergington.edu"})
+
+ assert response.status_code == 404
+ assert response.json()["detail"] == "Activity not found"
+
+
+def test_duplicate_signup_returns_400():
+ reset_activities()
+
+ activity_name = "Chess Club"
+ email = "michael@mergington.edu"
+
+ response = client.post(f"/activities/{activity_name}/signup", params={"email": email})
+
+ assert response.status_code == 400
+ assert response.json()["detail"] == "Student already signed up for this activity"