Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fastapi
uvicorn
httpx
watchfiles
watchfiles
pytest
59 changes: 59 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": "Join the competitive soccer team for training and matches",
"schedule": "Mondays, Wednesdays, 4:00 PM - 6:00 PM",
"max_participants": 18,
"participants": ["alex@mergington.edu", "mia@mergington.edu"]
},
"Swimming Club": {
"description": "Practice swim techniques and compete in friendly meets",
"schedule": "Tuesdays and Thursdays, 5:00 PM - 6:30 PM",
"max_participants": 24,
"participants": ["liam@mergington.edu", "ava@mergington.edu"]
},
"Art Club": {
"description": "Explore painting, drawing, and creative crafts",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 20,
"participants": ["noah@mergington.edu", "isabella@mergington.edu"]
},
"Drama Club": {
"description": "Rehearse scenes, and prepare performances for the school",
"schedule": "Thursdays, 4:00 PM - 6:00 PM",
"max_participants": 25,
"participants": ["lucas@mergington.edu", "amelia@mergington.edu"]
},
"Math Team": {
"description": "Solve challenging math problems and compete in tournaments",
"schedule": "Tuesdays, 3:30 PM - 5:00 PM",
"max_participants": 15,
"participants": ["ethan@mergington.edu", "sophia@mergington.edu"]
},
"Science Olympiad": {
"description": "Prepare experiments and science challenges for competition",
"schedule": "Fridays, 3:30 PM - 5:00 PM",
"max_participants": 20,
"participants": ["oliver@mergington.edu", "charlotte@mergington.edu"]
}
}

Expand All @@ -62,6 +98,29 @@ def signup_for_activity(activity_name: str, email: str):
# Get the specific activity
activity = activities[activity_name]

# Check if student is 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}"}


@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]

# Check if student is registered
if email not in activity["participants"]:
raise HTTPException(status_code=400, detail="Student is not registered for this activity")

# Remove student
activity["participants"].remove(email)
return {"message": f"Unregistered {email} from {activity_name}"}
41 changes: 41 additions & 0 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,53 @@ document.addEventListener("DOMContentLoaded", () => {
activityCard.className = "activity-card";

const spotsLeft = details.max_participants - details.participants.length;
const participantsList = details.participants
.map(participant => `
<li>
<span class="participant-email">${participant}</span>
<button class="delete-btn" data-activity="${name}" data-email="${participant}" title="Remove participant">×</button>
</li>
`)
.join('');

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-section">
<strong>Current Participants (${details.participants.length}/${details.max_participants}):</strong>
<ul class="participants-list">
${participantsList}
</ul>
</div>
`;

// Attach delete event listeners
activityCard.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.preventDefault();
const activity = btn.dataset.activity;
const email = btn.dataset.email;

try {
const response = await fetch(
`/activities/${encodeURIComponent(activity)}/unregister?email=${encodeURIComponent(email)}`,
{ method: 'DELETE' }
);

if (response.ok) {
fetchActivities();
} else {
const error = await response.json();
alert('Error: ' + (error.detail || 'Failed to unregister'));
}
} catch (error) {
alert('Error unregistering participant');
console.error('Error:', error);
}
});
});

activitiesList.appendChild(activityCard);

Expand Down Expand Up @@ -62,6 +102,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";
Expand Down
53 changes: 53 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,59 @@ section h3 {
margin-bottom: 8px;
}

.participants-section {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #ddd;
}

.participants-section strong {
display: block;
margin-bottom: 10px;
color: #1a237e;
font-size: 14px;
}

.participants-list {
list-style: none;
margin-left: 0;
padding-left: 0;
font-size: 14px;
color: #555;
}

.participants-list li {
margin-bottom: 8px;
word-break: break-all;
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 8px;
background-color: #f0f0f0;
border-radius: 4px;
}

.participant-email {
flex-grow: 1;
}

.delete-btn {
background-color: #d32f2f;
color: white;
border: none;
padding: 2px 8px;
font-size: 18px;
border-radius: 3px;
cursor: pointer;
margin-left: 8px;
line-height: 1;
transition: background-color 0.2s;
}

.delete-btn:hover {
background-color: #b71c1c;
}

.form-group {
margin-bottom: 15px;
}
Expand Down
Empty file added tests/__init__.py
Empty file.
88 changes: 88 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
Pytest configuration and fixtures for the FastAPI tests.
"""

import pytest
from fastapi.testclient import TestClient
from src import app as app_module
import copy


# Original activities state to restore after each test
ORIGINAL_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": "Join the competitive soccer team for training and matches",
"schedule": "Mondays, Wednesdays, 4:00 PM - 6:00 PM",
"max_participants": 18,
"participants": ["alex@mergington.edu", "mia@mergington.edu"]
},
"Swimming Club": {
"description": "Practice swim techniques and compete in friendly meets",
"schedule": "Tuesdays and Thursdays, 5:00 PM - 6:30 PM",
"max_participants": 24,
"participants": ["liam@mergington.edu", "ava@mergington.edu"]
},
"Art Club": {
"description": "Explore painting, drawing, and creative crafts",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 20,
"participants": ["noah@mergington.edu", "isabella@mergington.edu"]
},
"Drama Club": {
"description": "Rehearse scenes, and prepare performances for the school",
"schedule": "Thursdays, 4:00 PM - 6:00 PM",
"max_participants": 25,
"participants": ["lucas@mergington.edu", "amelia@mergington.edu"]
},
"Math Team": {
"description": "Solve challenging math problems and compete in tournaments",
"schedule": "Tuesdays, 3:30 PM - 5:00 PM",
"max_participants": 15,
"participants": ["ethan@mergington.edu", "sophia@mergington.edu"]
},
"Science Olympiad": {
"description": "Prepare experiments and science challenges for competition",
"schedule": "Fridays, 3:30 PM - 5:00 PM",
"max_participants": 20,
"participants": ["oliver@mergington.edu", "charlotte@mergington.edu"]
}
}


@pytest.fixture(autouse=True)
def reset_activities():
"""
Automatically reset activities to original state before each test.
"""
app_module.activities.clear()
app_module.activities.update(copy.deepcopy(ORIGINAL_ACTIVITIES))
yield
# Cleanup after test
app_module.activities.clear()
app_module.activities.update(copy.deepcopy(ORIGINAL_ACTIVITIES))


@pytest.fixture
def client():
"""
Provide a TestClient instance for testing the FastAPI app.
"""
return TestClient(app_module.app)
Loading
Loading