From 3645f34fee80a7b5142e103d68b9cd420227a9c4 Mon Sep 17 00:00:00 2001 From: Daniel Ueffing <94981829+DUeffing@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:38:06 +0000 Subject: [PATCH 1/5] added more activities --- src/app.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/app.py b/src/app.py index 4ebb1d9..dd2bb28 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"] + }, + "Soccer Team": { + "description": "Competitive soccer training and interschool matches", + "schedule": "Tuesdays and Thursdays, 4:00 PM - 5:30 PM", + "max_participants": 22, + "participants": ["lucas@mergington.edu", "mia@mergington.edu"] + }, + "Basketball Team": { + "description": "Basketball practice, drills, and league games", + "schedule": "Mondays and Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["james@mergington.edu", "ava@mergington.edu"] + }, + "Art Club": { + "description": "Explore painting, drawing, and mixed media art techniques", + "schedule": "Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["isabella@mergington.edu", "ethan@mergington.edu"] + }, + "Drama Club": { + "description": "Rehearse and perform plays, musicals, and improv theater", + "schedule": "Mondays and Fridays, 3:30 PM - 5:00 PM", + "max_participants": 20, + "participants": ["charlotte@mergington.edu", "liam@mergington.edu"] + }, + "Debate Team": { + "description": "Practice public speaking, argumentation, and compete in debate tournaments", + "schedule": "Tuesdays, 3:30 PM - 5:00 PM", + "max_participants": 16, + "participants": ["noah@mergington.edu", "amelia@mergington.edu"] + }, + "Science Olympiad": { + "description": "Prepare for and compete in science competitions across multiple disciplines", + "schedule": "Thursdays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["oliver@mergington.edu", "harper@mergington.edu"] } } @@ -62,6 +98,12 @@ 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") + + # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} + From 1fa750333157b1a22a58f44447c7246d7a70a0f5 Mon Sep 17 00:00:00 2001 From: Daniel Ueffing <94981829+DUeffing@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:47:57 +0000 Subject: [PATCH 2/5] Revert "added more activities" This reverts commit 3645f34fee80a7b5142e103d68b9cd420227a9c4. --- src/app.py | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/src/app.py b/src/app.py index dd2bb28..4ebb1d9 100644 --- a/src/app.py +++ b/src/app.py @@ -38,42 +38,6 @@ "schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM", "max_participants": 30, "participants": ["john@mergington.edu", "olivia@mergington.edu"] - }, - "Soccer Team": { - "description": "Competitive soccer training and interschool matches", - "schedule": "Tuesdays and Thursdays, 4:00 PM - 5:30 PM", - "max_participants": 22, - "participants": ["lucas@mergington.edu", "mia@mergington.edu"] - }, - "Basketball Team": { - "description": "Basketball practice, drills, and league games", - "schedule": "Mondays and Wednesdays, 3:30 PM - 5:00 PM", - "max_participants": 15, - "participants": ["james@mergington.edu", "ava@mergington.edu"] - }, - "Art Club": { - "description": "Explore painting, drawing, and mixed media art techniques", - "schedule": "Wednesdays, 3:30 PM - 5:00 PM", - "max_participants": 15, - "participants": ["isabella@mergington.edu", "ethan@mergington.edu"] - }, - "Drama Club": { - "description": "Rehearse and perform plays, musicals, and improv theater", - "schedule": "Mondays and Fridays, 3:30 PM - 5:00 PM", - "max_participants": 20, - "participants": ["charlotte@mergington.edu", "liam@mergington.edu"] - }, - "Debate Team": { - "description": "Practice public speaking, argumentation, and compete in debate tournaments", - "schedule": "Tuesdays, 3:30 PM - 5:00 PM", - "max_participants": 16, - "participants": ["noah@mergington.edu", "amelia@mergington.edu"] - }, - "Science Olympiad": { - "description": "Prepare for and compete in science competitions across multiple disciplines", - "schedule": "Thursdays, 3:30 PM - 5:00 PM", - "max_participants": 15, - "participants": ["oliver@mergington.edu", "harper@mergington.edu"] } } @@ -98,12 +62,6 @@ 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") - - # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} - From 1ccdfcc35d4100c09e2cc2f140e91eb17bf8254d Mon Sep 17 00:00:00 2001 From: Daniel Ueffing <94981829+DUeffing@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:51:37 +0000 Subject: [PATCH 3/5] Add extracurricular activities and signup validation --- src/app.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/app.py b/src/app.py index 4ebb1d9..efeeb8e 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"] + }, + "Soccer Team": { + "description": "Competitive soccer training and interschool matches", + "schedule": "Tuesdays and Thursdays, 4:00 PM - 5:30 PM", + "max_participants": 22, + "participants": ["lucas@mergington.edu", "liam@mergington.edu"] + }, + "Basketball Team": { + "description": "Basketball drills, scrimmages, and league games", + "schedule": "Mondays and Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["james@mergington.edu", "noah@mergington.edu"] + }, + "Art Club": { + "description": "Explore painting, drawing, and mixed media art techniques", + "schedule": "Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["mia@mergington.edu", "isabella@mergington.edu"] + }, + "Drama Club": { + "description": "Act in plays, learn stagecraft, and perform in school productions", + "schedule": "Mondays and Wednesdays, 4:00 PM - 5:30 PM", + "max_participants": 20, + "participants": ["charlotte@mergington.edu", "amelia@mergington.edu"] + }, + "Debate Club": { + "description": "Practice public speaking and compete in debate tournaments", + "schedule": "Thursdays, 3:30 PM - 5:00 PM", + "max_participants": 16, + "participants": ["ethan@mergington.edu", "alexander@mergington.edu"] + }, + "Math Olympiad": { + "description": "Solve challenging math problems and compete in mathematics competitions", + "schedule": "Tuesdays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["william@mergington.edu", "benjamin@mergington.edu"] } } @@ -62,6 +98,10 @@ 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") + # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} From a637a203134c35f0e39f462af139329df996130c Mon Sep 17 00:00:00 2001 From: Daniel Ueffing <94981829+DUeffing@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:01:47 +0000 Subject: [PATCH 4/5] Add participant removal functionality and update UI for activity signups --- src/app.py | 15 ++++++++++++ src/static/app.js | 51 +++++++++++++++++++++++++++++++++++++++ src/static/styles.css | 56 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/src/app.py b/src/app.py index efeeb8e..ca12620 100644 --- a/src/app.py +++ b/src/app.py @@ -105,3 +105,18 @@ def signup_for_activity(activity_name: str, email: str): # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} + + +@app.delete("/activities/{activity_name}/signup") +def unregister_from_activity(activity_name: str, email: str): + """Unregister a student from an activity""" + if activity_name not in activities: + raise HTTPException(status_code=404, detail="Activity not found") + + activity = activities[activity_name] + + if email not in activity["participants"]: + raise HTTPException(status_code=404, detail="Student not found in activity") + + activity["participants"].remove(email) + return {"message": f"Unregistered {email} from {activity_name}"} diff --git a/src/static/app.js b/src/static/app.js index dcc1e38..5e64c41 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -20,11 +20,26 @@ document.addEventListener("DOMContentLoaded", () => { const spotsLeft = details.max_participants - details.participants.length; + const participantsList = details.participants.length + ? details.participants.map(email => + `
  • + ${email} + +
  • ` + ).join("") + : "
  • No participants yet
  • "; + activityCard.innerHTML = `

    ${name}

    ${details.description}

    Schedule: ${details.schedule}

    Availability: ${spotsLeft} spots left

    +
    + Participants: + +
    `; activitiesList.appendChild(activityCard); @@ -41,6 +56,41 @@ document.addEventListener("DOMContentLoaded", () => { } } + // Handle remove participant button clicks + activitiesList.addEventListener("click", async (event) => { + const btn = event.target.closest(".remove-btn"); + if (!btn) return; + + const activity = btn.dataset.activity; + const email = btn.dataset.email; + + try { + const response = await fetch( + `/activities/${encodeURIComponent(activity)}/signup?email=${encodeURIComponent(email)}`, + { method: "DELETE" } + ); + + const result = await response.json(); + + if (response.ok) { + messageDiv.textContent = result.message; + messageDiv.className = "success"; + fetchActivities(); + } else { + messageDiv.textContent = result.detail || "An error occurred"; + messageDiv.className = "error"; + } + + messageDiv.classList.remove("hidden"); + setTimeout(() => messageDiv.classList.add("hidden"), 5000); + } catch (error) { + messageDiv.textContent = "Failed to remove participant. Please try again."; + messageDiv.className = "error"; + messageDiv.classList.remove("hidden"); + console.error("Error removing participant:", error); + } + }); + // Handle form submission signupForm.addEventListener("submit", async (event) => { event.preventDefault(); @@ -62,6 +112,7 @@ document.addEventListener("DOMContentLoaded", () => { messageDiv.textContent = result.message; messageDiv.className = "success"; signupForm.reset(); + 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..f2991a4 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -74,6 +74,62 @@ section h3 { margin-bottom: 8px; } +.participants-section { + margin-top: 10px; + padding-top: 8px; + border-top: 1px dashed #ddd; +} + +.participants-list { + list-style: none; + padding: 0; + margin: 6px 0 0 0; +} + +.participants-list li { + padding: 4px 8px; + position: relative; + font-size: 14px; + color: #555; + display: flex; + align-items: center; + justify-content: space-between; +} + +.participants-list li::before { + content: none; +} + +.participant-email { + flex: 1; +} + +.remove-btn { + background: none; + border: none; + color: #c62828; + cursor: pointer; + font-size: 14px; + padding: 2px 6px; + border-radius: 3px; + transition: background-color 0.2s; + line-height: 1; +} + +.remove-btn:hover { + background-color: #ffebee; + color: #b71c1c; +} + +.participants-list li.no-participants { + font-style: italic; + color: #999; +} + +.participants-list li.no-participants::before { + content: none; +} + .form-group { margin-bottom: 15px; } From e1d27c0828d161343d64c1700822dd6cd21621c6 Mon Sep 17 00:00:00 2001 From: Daniel Ueffing <94981829+DUeffing@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:15:09 +0000 Subject: [PATCH 5/5] Add tests for Mergington High School API endpoints and update requirements --- requirements.txt | 3 +- tests/__init__.py | 0 tests/test_app.py | 129 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tests/__init__.py 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/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..9bf64f1 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,129 @@ +"""Tests for the Mergington High School API endpoints.""" + +import copy +import pytest +from fastapi.testclient import TestClient +from src.app import app, activities + + +@pytest.fixture(autouse=True) +def reset_activities(): + """Reset the activities dict to its original state before each test.""" + original = copy.deepcopy(activities) + yield + activities.clear() + activities.update(original) + + +client = TestClient(app) + + +def test_get_activities(): + # Arrange + expected_keys = {"description", "schedule", "max_participants", "participants"} + + # Act + response = client.get("/activities") + + # Assert + assert response.status_code == 200 + data = response.json() + assert len(data) == 9 + for name, details in data.items(): + assert expected_keys.issubset(details.keys()), f"{name} missing keys" + + +def test_signup_success(): + # Arrange + activity_name = "Chess Club" + email = "newstudent@mergington.edu" + + # Act + response = client.post( + f"/activities/{activity_name}/signup", + params={"email": email}, + ) + + # Assert + assert response.status_code == 200 + assert email in activities[activity_name]["participants"] + + +def test_signup_duplicate(): + # Arrange + activity_name = "Chess Club" + email = "michael@mergington.edu" # already in participants + + # Act + response = client.post( + f"/activities/{activity_name}/signup", + params={"email": email}, + ) + + # Assert + assert response.status_code == 400 + assert "already signed up" in response.json()["detail"].lower() + + +def test_signup_nonexistent_activity(): + # Arrange + activity_name = "Nonexistent Activity" + email = "someone@mergington.edu" + + # Act + response = client.post( + f"/activities/{activity_name}/signup", + params={"email": email}, + ) + + # Assert + assert response.status_code == 404 + assert "not found" in response.json()["detail"].lower() + + +def test_unregister_success(): + # Arrange + activity_name = "Chess Club" + email = "michael@mergington.edu" # existing participant + + # Act + response = client.delete( + f"/activities/{activity_name}/signup", + params={"email": email}, + ) + + # Assert + assert response.status_code == 200 + assert email not in activities[activity_name]["participants"] + + +def test_unregister_not_found(): + # Arrange + activity_name = "Chess Club" + email = "nonexistent@mergington.edu" + + # Act + response = client.delete( + f"/activities/{activity_name}/signup", + params={"email": email}, + ) + + # Assert + assert response.status_code == 404 + assert "not found" in response.json()["detail"].lower() + + +def test_unregister_nonexistent_activity(): + # Arrange + activity_name = "Nonexistent Activity" + email = "someone@mergington.edu" + + # Act + response = client.delete( + f"/activities/{activity_name}/signup", + params={"email": email}, + ) + + # Assert + assert response.status_code == 404 + assert "not found" in response.json()["detail"].lower()