diff --git a/.vscode/launch.json b/.vscode/launch.json index 0bce79f..eb5e8b3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,7 @@ "args": [ "src.app:app", "--reload", - "--reload-include", - "src/static/*" + "--reload-include=src/static/*" ], "jinja": true } 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/src/app.py b/src/app.py index 4ebb1d9..d4a5c3d 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": "Join the school's basketball team and compete in local leagues", + "schedule": "Tuesdays and Thursdays, 4:00 PM - 6:00 PM", + "max_participants": 15, + "participants": ["liam@mergington.edu"] + }, + "Soccer Club": { + "description": "Practice soccer skills and play friendly matches", + "schedule": "Wednesdays, 3:30 PM - 5:30 PM", + "max_participants": 18, + "participants": ["noah@mergington.edu"] + }, + "Art Club": { + "description": "Explore painting, drawing, and other visual arts", + "schedule": "Mondays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["ava@mergington.edu"] + }, + "Drama Society": { + "description": "Participate in theater productions and acting workshops", + "schedule": "Fridays, 4:00 PM - 6:00 PM", + "max_participants": 20, + "participants": ["mia@mergington.edu"] + }, + "Math Olympiad": { + "description": "Prepare for math competitions and solve challenging problems", + "schedule": "Thursdays, 3:30 PM - 5:00 PM", + "max_participants": 10, + "participants": ["william@mergington.edu"] + }, + "Debate Club": { + "description": "Develop public speaking and argumentation skills", + "schedule": "Wednesdays, 4:00 PM - 5:30 PM", + "max_participants": 16, + "participants": ["charlotte@mergington.edu"] } } @@ -62,6 +98,10 @@ def signup_for_activity(activity_name: str, email: str): # Get the specific activity activity = activities[activity_name] + # Validate if student is already signed up + if email in activity["participants"]: + raise HTTPException(status_code=400, detail="Student is already signed up for this activity") + # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} diff --git a/src/static/app.js b/src/static/app.js index dcc1e38..e2775d3 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -20,11 +20,31 @@ 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: + +
+ `; + } else { + participantsHTML = ` +
+ No participants yet +
+ `; + } + activityCard.innerHTML = `

${name}

${details.description}

Schedule: ${details.schedule}

Availability: ${spotsLeft} spots left

+ ${participantsHTML} `; activitiesList.appendChild(activityCard); diff --git a/src/static/styles.css b/src/static/styles.css index a533b32..5dba73f 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -74,6 +74,31 @@ 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 18px; + padding-left: 0; + list-style-type: disc; + color: #333; + font-size: 15px; +} +.participants-section.empty { + background-color: #f9f9f9; + color: #888; + border: 1px dashed #ccc; + font-style: italic; +} + .form-group { margin-bottom: 15px; } 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..af7d0c2 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,67 @@ +import pytest +from fastapi.testclient import TestClient +from src.app import app, activities + +client = TestClient(app) + +# Helper to reset activities for test isolation +def reset_participants(): + for activity in activities.values(): + if isinstance(activity.get("participants"), list): + activity["participants"] = [] + +def test_get_activities(): + # Arrange + reset_participants() + # Act + response = client.get("/activities") + # Assert + assert response.status_code == 200 + data = response.json() + assert isinstance(data, dict) + assert "Chess Club" in data + +def test_signup_for_activity(): + # Arrange + reset_participants() + email = "testuser@mergington.edu" + # Act + response = client.post(f"/activities/Chess Club/signup?email={email}") + # Assert + assert response.status_code == 200 + assert email in activities["Chess Club"]["participants"] + + +def test_signup_duplicate(): + # Arrange + reset_participants() + email = "testuser@mergington.edu" + client.post(f"/activities/Chess Club/signup?email={email}") + # Act + response = client.post(f"/activities/Chess Club/signup?email={email}") + # Assert + assert response.status_code == 400 + assert response.json()["detail"] == "Student is already signed up for this activity" + + +def test_unregister_participant(): + # Arrange + reset_participants() + email = "testuser@mergington.edu" + client.post(f"/activities/Chess Club/signup?email={email}") + # Act + response = client.delete(f"/activities/Chess Club/unregister?email={email}") + # Assert + assert response.status_code == 200 + assert email not in activities["Chess Club"]["participants"] + + +def test_unregister_nonexistent(): + # Arrange + reset_participants() + email = "notfound@mergington.edu" + # Act + response = client.delete(f"/activities/Chess Club/unregister?email={email}") + # Assert + assert response.status_code == 404 + assert response.json()["detail"] == "Participant not found in this activity"