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

    +
    + Participants: + +
    `; 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"