From 5f6bdddd23f2c0cd64ba6bc78dc6c0b6a175fd28 Mon Sep 17 00:00:00 2001 From: HarshitMCT Date: Thu, 19 Feb 2026 11:04:56 +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..39eb3ed 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": "Practice soccer skills and compete in inter-school matches", + "schedule": "Mondays and Wednesdays, 4:00 PM - 5:30 PM", + "max_participants": 22, + "participants": ["liam@mergington.edu", "noah@mergington.edu"] + }, + "Basketball Club": { + "description": "Develop teamwork and basketball fundamentals", + "schedule": "Tuesdays and Fridays, 4:00 PM - 5:30 PM", + "max_participants": 15, + "participants": ["ava@mergington.edu", "isabella@mergington.edu"] + }, + "Drama Club": { + "description": "Explore acting, stage presence, and theater production", + "schedule": "Thursdays, 3:30 PM - 5:00 PM", + "max_participants": 18, + "participants": ["mia@mergington.edu", "charlotte@mergington.edu"] + }, + "Painting Workshop": { + "description": "Learn painting techniques and create visual art projects", + "schedule": "Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 16, + "participants": ["amelia@mergington.edu", "harper@mergington.edu"] + }, + "Debate Team": { + "description": "Build public speaking and critical thinking through debates", + "schedule": "Mondays, 3:30 PM - 5:00 PM", + "max_participants": 14, + "participants": ["elijah@mergington.edu", "james@mergington.edu"] + }, + "Math Olympiad": { + "description": "Solve advanced math problems and prepare for competitions", + "schedule": "Fridays, 3:30 PM - 5:00 PM", + "max_participants": 12, + "participants": ["lucas@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 for this activity") + # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} From 5bed9f9c19c1141395feffe0684cdbfa87c9b63c Mon Sep 17 00:00:00 2001 From: HarshitMCT Date: Thu, 19 Feb 2026 11:23:28 +0000 Subject: [PATCH 2/3] Enhance activity management: add participant list display and removal functionality --- src/app.py | 144 ++++++++++++++++++++++-------------------- src/static/app.js | 54 ++++++++++++++++ src/static/styles.css | 63 ++++++++++++++++++ 3 files changed, 193 insertions(+), 68 deletions(-) diff --git a/src/app.py b/src/app.py index 39eb3ed..c0983c9 100644 --- a/src/app.py +++ b/src/app.py @@ -21,60 +21,61 @@ # In-memory activity database activities = { - "Chess Club": { - "description": "Learn strategies and compete in chess tournaments", - "schedule": "Fridays, 3:30 PM - 5:00 PM", - "max_participants": 12, - "participants": ["michael@mergington.edu", "daniel@mergington.edu"] - }, - "Programming Class": { - "description": "Learn programming fundamentals and build software projects", - "schedule": "Tuesdays and Thursdays, 3:30 PM - 4:30 PM", - "max_participants": 20, - "participants": ["emma@mergington.edu", "sophia@mergington.edu"] - }, - "Gym Class": { - "description": "Physical education and sports activities", - "schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM", - "max_participants": 30, - "participants": ["john@mergington.edu", "olivia@mergington.edu"] - }, - "Soccer Team": { - "description": "Practice soccer skills and compete in inter-school matches", - "schedule": "Mondays and Wednesdays, 4:00 PM - 5:30 PM", - "max_participants": 22, - "participants": ["liam@mergington.edu", "noah@mergington.edu"] - }, - "Basketball Club": { - "description": "Develop teamwork and basketball fundamentals", - "schedule": "Tuesdays and Fridays, 4:00 PM - 5:30 PM", - "max_participants": 15, - "participants": ["ava@mergington.edu", "isabella@mergington.edu"] - }, - "Drama Club": { - "description": "Explore acting, stage presence, and theater production", - "schedule": "Thursdays, 3:30 PM - 5:00 PM", - "max_participants": 18, - "participants": ["mia@mergington.edu", "charlotte@mergington.edu"] - }, - "Painting Workshop": { - "description": "Learn painting techniques and create visual art projects", - "schedule": "Wednesdays, 3:30 PM - 5:00 PM", - "max_participants": 16, - "participants": ["amelia@mergington.edu", "harper@mergington.edu"] - }, - "Debate Team": { - "description": "Build public speaking and critical thinking through debates", - "schedule": "Mondays, 3:30 PM - 5:00 PM", - "max_participants": 14, - "participants": ["elijah@mergington.edu", "james@mergington.edu"] - }, - "Math Olympiad": { - "description": "Solve advanced math problems and prepare for competitions", - "schedule": "Fridays, 3:30 PM - 5:00 PM", - "max_participants": 12, - "participants": ["lucas@mergington.edu", "benjamin@mergington.edu"] - } + "Chess Club": { + "description": "Learn strategies and compete in chess tournaments", + "schedule": "Fridays, 3:30 PM - 5:00 PM", + "max_participants": 12, + "participants": ["michael@mergington.edu", "daniel@mergington.edu"] + }, + "Programming Class": { + "description": "Learn programming fundamentals and build software projects", + "schedule": "Tuesdays and Thursdays, 3:30 PM - 4:30 PM", + "max_participants": 20, + "participants": ["emma@mergington.edu", "sophia@mergington.edu"] + }, + "Gym Class": { + "description": "Physical education and sports activities", + "schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM", + "max_participants": 30, + "participants": ["john@mergington.edu", "olivia@mergington.edu"] + }, + "Soccer Team": { + "description": "Practice soccer skills and compete in inter-school matches", + "schedule": "Mondays and Wednesdays, 4:00 PM - 5:30 PM", + "max_participants": 22, + "participants": ["liam@mergington.edu", "noah@mergington.edu"] + }, + "Basketball Club": { + "description": "Develop teamwork and basketball fundamentals", + "schedule": "Tuesdays and Fridays, 4:00 PM - 5:30 PM", + "max_participants": 15, + "participants": ["ava@mergington.edu", "isabella@mergington.edu"] + }, + "Drama Club": { + "description": "Explore acting, stage presence, and theater production", + "schedule": "Thursdays, 3:30 PM - 5:00 PM", + "max_participants": 18, + "participants": ["mia@mergington.edu", "charlotte@mergington.edu"] + }, + "Painting Workshop": { + "description": "Learn painting techniques and create visual art projects", + "schedule": "Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 16, + "participants": ["amelia@mergington.edu", "harper@mergington.edu"] + }, + "Debate Team": { + "description": "Build public speaking and critical thinking through debates", + "schedule": "Mondays, 3:30 PM - 5:00 PM", + "max_participants": 14, + "participants": ["elijah@mergington.edu", "james@mergington.edu"] + }, + "Math Olympiad": { + "description": "Solve advanced math problems and prepare for competitions", + "schedule": "Fridays, 3:30 PM - 5:00 PM", + "max_participants": 12, + "participants": ["lucas@mergington.edu", "benjamin@mergington.edu"] + } + } @@ -88,20 +89,27 @@ def get_activities(): return activities + +# POST: Register participant @app.post("/activities/{activity_name}/signup") def signup_for_activity(activity_name: str, email: str): - """Sign up a student for 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 not already signed up - if email in activity["participants"]: - raise HTTPException(status_code=400, detail="Student already signed up for this activity") + """Sign up a student for an activity""" + if activity_name not in activities: + raise HTTPException(status_code=404, detail="Activity not found") + activity = activities[activity_name] + if email in activity["participants"]: + raise HTTPException(status_code=400, detail="Student already signed up for this activity") + activity["participants"].append(email) + return {"message": f"Signed up {email} for {activity_name}"} - # Add student - activity["participants"].append(email) - return {"message": f"Signed up {email} for {activity_name}"} +# DELETE: Unregister participant +@app.delete("/activities/{activity_name}/signup") +def unregister_from_activity(activity_name: str, email: str): + """Remove 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 registered for this activity") + activity["participants"].remove(email) + return {"message": f"Removed {email} from {activity_name}"} diff --git a/src/static/app.js b/src/static/app.js index dcc1e38..ef371c2 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -20,14 +20,67 @@ document.addEventListener("DOMContentLoaded", () => { const spotsLeft = details.max_participants - details.participants.length; + // Create participants list HTML + let participantsHTML = ""; + if (details.participants && details.participants.length > 0) { + participantsHTML = ` +
+ Participants: +
    + ${details.participants.map(p => ` +
  • + ${p} + +
  • + `).join("")} +
+
+ `; + } else { + participantsHTML = ` +
+ No participants yet +
+ `; + } + activityCard.innerHTML = `

${name}

${details.description}

Schedule: ${details.schedule}

Availability: ${spotsLeft} spots left

+ ${participantsHTML} `; activitiesList.appendChild(activityCard); + activitiesList.appendChild(activityCard); + + // Add delete event listeners after card is in DOM + const deleteButtons = activityCard.querySelectorAll('.delete-participant'); + deleteButtons.forEach(btn => { + btn.addEventListener('click', async (e) => { + e.preventDefault(); + e.stopPropagation(); + const email = btn.getAttribute('data-email'); + const activity = btn.getAttribute('data-activity'); + if (!email || !activity) return; + try { + const response = await fetch(`/activities/${encodeURIComponent(activity)}/signup?email=${encodeURIComponent(email)}`, { + method: 'DELETE', + }); + if (response.ok) { + fetchActivities(); // Refresh list + } else { + const result = await response.json(); + alert(result.detail || 'Failed to remove participant.'); + } + } catch (error) { + alert('Failed to remove participant.'); + } + }); + }); // Add option to select dropdown const option = document.createElement("option"); @@ -62,6 +115,7 @@ document.addEventListener("DOMContentLoaded", () => { messageDiv.textContent = result.message; messageDiv.className = "success"; signupForm.reset(); + fetchActivities(); // Refresh activities list after successful signup } 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..14c4dec 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -74,6 +74,69 @@ section h3 { margin-bottom: 8px; } +.participants-section { + margin-top: 10px; + padding: 10px; + background-color: #eef3fa; + border-radius: 4px; + border: 1px solid #c5cae9; +} + +.participants-section strong { + color: #3949ab; + font-size: 15px; +} + +.participants-list { + margin: 8px 0 0 0; + padding-left: 0; + list-style-type: none; + color: #333; + font-size: 15px; +} + +.no-bullets { + list-style-type: none; + padding-left: 0; +} + +.participant-item { + display: flex; + align-items: center; + margin-bottom: 4px; +} + +.participant-email { + flex: 1; + word-break: break-all; +} + +.delete-participant { + background: none; + border: none; + color: #c62828; + font-size: 20px; + margin-left: 8px; + cursor: pointer; + padding: 2px 6px; + border-radius: 50%; + transition: background 0.2s; + line-height: 1; +} + +.delete-participant:hover { + background: #ffebee; + color: #b71c1c; +} + +.participants-section.empty { + background: none; + border: none; + color: #888; + font-style: italic; + padding-left: 0; +} + .form-group { margin-bottom: 15px; } From 3eb390bb032afaf06bcaf18c1c4ab941297cac0e Mon Sep 17 00:00:00 2001 From: HarshitMCT Date: Thu, 19 Feb 2026 11:28:19 +0000 Subject: [PATCH 3/3] Add tests for activity management: implement signup and unregister functionality --- requirements.txt | 2 ++ tests/test_app.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 tests/test_app.py diff --git a/requirements.txt b/requirements.txt index 5d9efb5..2442353 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +# For testing +pytest fastapi uvicorn httpx diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..df85791 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,91 @@ +import pytest +from fastapi.testclient import TestClient +from src.app import app, activities + +client = TestClient(app) + +@pytest.fixture(autouse=True) +def reset_activities(): + # Arrange: Reset the in-memory activities before each test + for activity in activities.values(): + activity['participants'].clear() + # Optionally, repopulate with initial data if needed + +# --- Root endpoint --- +def test_root_redirect(): + # Act + response = client.get("/") + # Assert + assert response.status_code in (200, 307, 308) + +# --- List activities --- +def test_get_activities(): + # Act + response = client.get("/activities") + # Assert + assert response.status_code == 200 + data = response.json() + assert isinstance(data, dict) + assert "Chess Club" in data + +# --- Signup for activity --- +def test_signup_success(): + # Arrange + email = "test1@mergington.edu" + activity = "Chess Club" + # Act + response = client.post(f"/activities/{activity}/signup?email={email}") + # Assert + assert response.status_code == 200 + assert email in activities[activity]["participants"] + +# --- Signup duplicate --- +def test_signup_duplicate(): + # Arrange + email = "test2@mergington.edu" + activity = "Chess Club" + client.post(f"/activities/{activity}/signup?email={email}") + # Act + response = client.post(f"/activities/{activity}/signup?email={email}") + # Assert + assert response.status_code == 400 + assert "already signed up" in response.json()["detail"] + +# --- Signup for non-existent activity --- +def test_signup_activity_not_found(): + # Act + response = client.post("/activities/Nonexistent/signup?email=foo@bar.com") + # Assert + assert response.status_code == 404 + assert "Activity not found" in response.json()["detail"] + +# --- Unregister participant --- +def test_unregister_success(): + # Arrange + email = "test3@mergington.edu" + activity = "Chess Club" + client.post(f"/activities/{activity}/signup?email={email}") + # Act + response = client.delete(f"/activities/{activity}/signup?email={email}") + # Assert + assert response.status_code == 200 + assert email not in activities[activity]["participants"] + +# --- Unregister not registered --- +def test_unregister_not_registered(): + # Arrange + email = "notregistered@mergington.edu" + activity = "Chess Club" + # Act + response = client.delete(f"/activities/{activity}/signup?email={email}") + # Assert + assert response.status_code == 404 + assert "not registered" in response.json()["detail"] + +# --- Unregister from non-existent activity --- +def test_unregister_activity_not_found(): + # Act + response = client.delete("/activities/Nonexistent/signup?email=foo@bar.com") + # Assert + assert response.status_code == 404 + assert "Activity not found" in response.json()["detail"]