-
Notifications
You must be signed in to change notification settings - Fork 430
Improve student activity reg system #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| fastapi | ||
| uvicorn | ||
| pytest | ||
| httpx |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 team with regular matches and tournaments", | ||
| "schedule": "Tuesdays and Thursdays, 4:00 PM - 6:00 PM", | ||
| "max_participants": 25, | ||
| "participants": ["james@mergington.edu", "liam@mergington.edu"] | ||
| }, | ||
| "Swimming Club": { | ||
| "description": "Swim training and competitive meets", | ||
| "schedule": "Mondays and Wednesdays, 3:30 PM - 5:00 PM", | ||
| "max_participants": 20, | ||
| "participants": ["ava@mergington.edu", "noah@mergington.edu"] | ||
| }, | ||
| "Art Studio": { | ||
| "description": "Painting, drawing, and mixed media art projects", | ||
| "schedule": "Thursdays, 3:30 PM - 5:30 PM", | ||
| "max_participants": 15, | ||
| "participants": ["isabella@mergington.edu", "mia@mergington.edu"] | ||
| }, | ||
| "Drama Club": { | ||
| "description": "Theater performances, acting workshops, and stage production", | ||
| "schedule": "Tuesdays and Fridays, 3:30 PM - 5:00 PM", | ||
| "max_participants": 30, | ||
| "participants": ["ethan@mergington.edu", "charlotte@mergington.edu"] | ||
| }, | ||
| "Robotics Team": { | ||
| "description": "Build and program robots for regional competitions", | ||
| "schedule": "Wednesdays, 3:30 PM - 6:00 PM", | ||
| "max_participants": 18, | ||
| "participants": ["william@mergington.edu", "benjamin@mergington.edu"] | ||
| }, | ||
| "Debate Club": { | ||
| "description": "Develop critical thinking and public speaking through competitive debates", | ||
| "schedule": "Mondays, 3:30 PM - 5:00 PM", | ||
| "max_participants": 16, | ||
| "participants": ["amelia@mergington.edu", "lucas@mergington.edu"] | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -62,6 +98,29 @@ def signup_for_activity(activity_name: str, email: str): | |
| # Get the specific activity | ||
| activity = activities[activity_name] | ||
|
|
||
| # Validate student is not already signed up for the activity | ||
| if email in activity["participants"]: | ||
| raise HTTPException(status_code=400, detail="Already signed up for this activity") | ||
|
|
||
| # Add student | ||
| activity["participants"].append(email) | ||
| return {"message": f"Signed up {email} for {activity_name}"} | ||
|
Comment on lines
+101
to
107
|
||
|
|
||
|
|
||
| @app.delete("/activities/{activity_name}/unregister") | ||
| def unregister_from_activity(activity_name: str, email: str): | ||
| """Unregister a student from 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 signed up for the activity | ||
| if email not in activity["participants"]: | ||
| raise HTTPException(status_code=400, detail="Not signed up for this activity") | ||
|
|
||
| # Remove student | ||
| activity["participants"].remove(email) | ||
| return {"message": f"Unregistered {email} from {activity_name}"} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,18 +13,35 @@ document.addEventListener("DOMContentLoaded", () => { | |
| // Clear loading message | ||
| activitiesList.innerHTML = ""; | ||
|
|
||
| // Clear existing options from dropdown (except the default option) | ||
| while (activitySelect.options.length > 1) { | ||
| activitySelect.remove(1); | ||
| } | ||
|
|
||
| // 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 participantsMarkup = details.participants?.length | ||
| ? `<ul class="participants-list"> | ||
| ${details.participants.map((p) => `<li>${p} <button class="delete-btn" data-activity="${name}" data-email="${p}" aria-label="Delete ${p}">✕</button></li>`).join("")} | ||
|
||
| </ul>` | ||
| : `<p class="participants-empty">Be the first to join this activity!</p>`; | ||
|
|
||
| activityCard.innerHTML = ` | ||
| <h4>${name}</h4> | ||
| <p>${details.description}</p> | ||
| <p><strong>Schedule:</strong> ${details.schedule}</p> | ||
| <p><strong>Availability:</strong> ${spotsLeft} spots left</p> | ||
| <div class="participants"> | ||
| <div class="participants-header"> | ||
| <span class="participants-title">Participants</span> | ||
| <span class="participants-count">${details.participants.length}</span> | ||
| </div> | ||
| ${participantsMarkup} | ||
| </div> | ||
|
Comment on lines
+27
to
+44
|
||
| `; | ||
|
|
||
| activitiesList.appendChild(activityCard); | ||
|
|
@@ -35,6 +52,36 @@ document.addEventListener("DOMContentLoaded", () => { | |
| option.textContent = name; | ||
| activitySelect.appendChild(option); | ||
| }); | ||
|
|
||
| // Add event listeners for delete buttons using event delegation | ||
| activitiesList.addEventListener("click", async (event) => { | ||
| if (event.target.classList.contains("delete-btn")) { | ||
| const activityName = event.target.dataset.activity; | ||
| const email = event.target.dataset.email; | ||
|
|
||
| if (confirm(`Remove ${email} from ${activityName}?`)) { | ||
| try { | ||
| const response = await fetch( | ||
| `/activities/${encodeURIComponent(activityName)}/unregister?email=${encodeURIComponent(email)}`, | ||
| { | ||
| method: "DELETE", | ||
| } | ||
| ); | ||
|
|
||
| if (response.ok) { | ||
| // Refresh the activities list | ||
| fetchActivities(); | ||
| } else { | ||
| const result = await response.json(); | ||
| alert(result.detail || "Failed to unregister participant"); | ||
| } | ||
| } catch (error) { | ||
| alert("Failed to unregister participant"); | ||
| console.error("Error unregistering:", error); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
Comment on lines
+56
to
+84
|
||
| } catch (error) { | ||
| activitiesList.innerHTML = "<p>Failed to load activities. Please try again later.</p>"; | ||
| console.error("Error fetching activities:", error); | ||
|
|
@@ -62,6 +109,8 @@ document.addEventListener("DOMContentLoaded", () => { | |
| messageDiv.textContent = result.message; | ||
| messageDiv.className = "success"; | ||
| signupForm.reset(); | ||
| // Refresh the activities list to show the new participant | ||
| fetchActivities(); | ||
| } else { | ||
| messageDiv.textContent = result.detail || "An error occurred"; | ||
| messageDiv.className = "error"; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| # Add src directory to path so we can import app | ||
| sys.path.insert(0, str(Path(__file__).parent.parent / "src")) | ||
|
|
||
| import pytest | ||
| from fastapi.testclient import TestClient | ||
| from app import app | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def client(): | ||
| """Create a test client for the FastAPI app""" | ||
| return TestClient(app) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def reset_activities(): | ||
| """Reset activities to initial state before each test""" | ||
| from app import activities | ||
|
|
||
| initial_state = { | ||
| "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": "Competitive soccer team with regular matches and tournaments", | ||
| "schedule": "Tuesdays and Thursdays, 4:00 PM - 6:00 PM", | ||
| "max_participants": 25, | ||
| "participants": ["james@mergington.edu", "liam@mergington.edu"] | ||
| }, | ||
| "Swimming Club": { | ||
| "description": "Swim training and competitive meets", | ||
| "schedule": "Mondays and Wednesdays, 3:30 PM - 5:00 PM", | ||
| "max_participants": 20, | ||
| "participants": ["ava@mergington.edu", "noah@mergington.edu"] | ||
| }, | ||
| "Art Studio": { | ||
| "description": "Painting, drawing, and mixed media art projects", | ||
| "schedule": "Thursdays, 3:30 PM - 5:30 PM", | ||
| "max_participants": 15, | ||
| "participants": ["isabella@mergington.edu", "mia@mergington.edu"] | ||
| }, | ||
| "Drama Club": { | ||
| "description": "Theater performances, acting workshops, and stage production", | ||
| "schedule": "Tuesdays and Fridays, 3:30 PM - 5:00 PM", | ||
| "max_participants": 30, | ||
| "participants": ["ethan@mergington.edu", "charlotte@mergington.edu"] | ||
| }, | ||
| "Robotics Team": { | ||
| "description": "Build and program robots for regional competitions", | ||
| "schedule": "Wednesdays, 3:30 PM - 6:00 PM", | ||
| "max_participants": 18, | ||
| "participants": ["william@mergington.edu", "benjamin@mergington.edu"] | ||
| }, | ||
| "Debate Club": { | ||
| "description": "Develop critical thinking and public speaking through competitive debates", | ||
| "schedule": "Mondays, 3:30 PM - 5:00 PM", | ||
| "max_participants": 16, | ||
| "participants": ["amelia@mergington.edu", "lucas@mergington.edu"] | ||
| } | ||
| } | ||
|
|
||
| # Clear and reset | ||
| activities.clear() | ||
| activities.update(initial_state) | ||
| yield | ||
| # Reset after test | ||
| activities.clear() | ||
| activities.update(initial_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The signup endpoint does not validate whether the activity has reached its maximum capacity before adding a new participant. This allows more participants to sign up than
max_participantsallows.Add a validation check: