Merge pull request #3 from jlengrand/accelerate-with-copilot

Accelerate with copilot
This commit is contained in:
julien Lengrand-Lambert
2025-10-10 16:43:36 +02:00
committed by GitHub
5 changed files with 176 additions and 0 deletions

View File

@@ -1,2 +1,5 @@
fastapi
uvicorn
testclient
httpx
pytest

View File

@@ -38,6 +38,45 @@ activities = {
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
},
# Sports related activities
"Soccer Team": {
"description": "Join the school soccer team and compete in matches",
"schedule": "Wednesdays, 4:00 PM - 5:30 PM",
"max_participants": 18,
"participants": ["lucas@mergington.edu", "mia@mergington.edu"]
},
"Basketball Club": {
"description": "Practice basketball skills and play friendly games",
"schedule": "Thursdays, 3:30 PM - 5:00 PM",
"max_participants": 15,
"participants": ["liam@mergington.edu", "ava@mergington.edu"]
},
# Artistic activities
"Art Workshop": {
"description": "Explore painting, drawing, and sculpture techniques",
"schedule": "Mondays, 4:00 PM - 5:30 PM",
"max_participants": 16,
"participants": ["ella@mergington.edu", "noah@mergington.edu"]
},
"Drama Club": {
"description": "Act, direct, and produce school plays and performances",
"schedule": "Tuesdays, 3:30 PM - 5:00 PM",
"max_participants": 20,
"participants": ["isabella@mergington.edu", "jack@mergington.edu"]
},
# Intellectual activities
"Mathletes": {
"description": "Compete in math competitions and solve challenging problems",
"schedule": "Fridays, 4:00 PM - 5:00 PM",
"max_participants": 10,
"participants": ["oliver@mergington.edu", "charlotte@mergington.edu"]
},
"Science Club": {
"description": "Conduct experiments and explore scientific concepts",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 14,
"participants": ["henry@mergington.edu", "amelia@mergington.edu"]
}
}
@@ -55,6 +94,12 @@ def get_activities():
@app.post("/activities/{activity_name}/signup")
def signup_for_activity(activity_name: str, email: str):
"""Sign up a student for an activity"""
# Validate student is not already signed up
for activity in activities.values():
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up for an activity")
# Validate activity exists
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")
@@ -65,3 +110,15 @@ def signup_for_activity(activity_name: str, email: str):
# Add student
activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}
# Unregister endpoint
@app.post("/activities/{activity_name}/unregister")
def unregister_from_activity(activity_name: str, email: str):
"""Remove a student from an activity"""
activity = activities.get(activity_name)
if not activity:
raise HTTPException(status_code=404, detail="Activity not found")
if email not in activity["participants"]:
raise HTTPException(status_code=404, detail="Participant not found in activity")
activity["participants"].remove(email)
return {"message": f"{email} removed from {activity_name}"}

View File

@@ -20,13 +20,53 @@ document.addEventListener("DOMContentLoaded", () => {
const spotsLeft = details.max_participants - details.participants.length;
// Participants section
let participantsHTML = `
<div class="participants-section" style="margin-top: 1em;">
<strong style="color: #3949ab; font-size: 1.05em;">Participants:</strong>
${
details.participants.length > 0
? `<ul style="margin: 0.5em 0 0 1em; padding: 0; list-style-type: none;">
${details.participants.map(p => `
<li style="margin-bottom: 2px; color: #333; display: flex; align-items: center;">
<span>${p}</span>
<span class="delete-participant" data-activity="${name}" data-participant="${p}" style="cursor:pointer; margin-left:8px; color:#e53935; font-size:1.1em;" title="Remove participant">&#128465;</span>
</li>`).join("")}
</ul>`
: `<p style="margin: 0.5em 0 0 1em; color: #888; font-style: italic;">No participants yet.</p>`
}
</div>
`;
activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
${participantsHTML}
`;
// Add event listener for delete icons
activityCard.querySelectorAll('.delete-participant').forEach(icon => {
icon.addEventListener('click', async (e) => {
const activityName = icon.getAttribute('data-activity');
const participant = icon.getAttribute('data-participant');
try {
const response = await fetch(`/activities/${encodeURIComponent(activityName)}/unregister?email=${encodeURIComponent(participant)}`, {
method: 'POST',
});
const result = await response.json();
if (response.ok) {
fetchActivities(); // Refresh list
} else {
alert(result.detail || 'Failed to remove participant.');
}
} catch (err) {
alert('Error removing participant.');
}
});
});
activitiesList.appendChild(activityCard);
// Add option to select dropdown

View File

@@ -142,3 +142,34 @@ footer {
padding: 20px;
color: #666;
}
.participants-section {
background: #eef2fa;
border-radius: 4px;
padding: 10px 12px;
margin-top: 12px;
}
.participants-section strong {
display: block;
margin-bottom: 6px;
color: #3949ab;
}
.participants-section ul {
margin-left: 1em;
padding-left: 0;
}
.participants-section li {
color: #333;
margin-bottom: 2px;
list-style-type: disc;
}
.participants-section p {
color: #888;
margin-left: 1em;
font-style: italic;
margin-bottom: 0;
}

45
tests/test_app.py Normal file
View File

@@ -0,0 +1,45 @@
import pytest
from fastapi.testclient import TestClient
from src.app import app
client = TestClient(app)
def test_get_activities():
response = client.get("/activities")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)
assert "Chess Club" in data
def test_signup_for_activity():
email = "newstudent@mergington.edu"
activity = "Chess Club"
response = client.post(f"/activities/{activity}/signup?email={email}")
assert response.status_code == 200
assert email in response.json()["message"]
# Try signing up again (should fail)
response2 = client.post(f"/activities/{activity}/signup?email={email}")
assert response2.status_code == 400
def test_unregister_from_activity():
email = "newstudent@mergington.edu"
activity = "Chess Club"
# Unregister
response = client.post(f"/activities/{activity}/unregister?email={email}")
assert response.status_code == 200
assert email in response.json()["message"]
# Try unregistering again (should fail)
response2 = client.post(f"/activities/{activity}/unregister?email={email}")
assert response2.status_code == 404
def test_signup_invalid_activity():
response = client.post("/activities/Nonexistent/signup?email=test@mergington.edu")
assert response.status_code == 404
def test_unregister_invalid_activity():
response = client.post("/activities/Nonexistent/unregister?email=test@mergington.edu")
assert response.status_code == 404
def test_unregister_invalid_participant():
response = client.post("/activities/Chess Club/unregister?email=notfound@mergington.edu")
assert response.status_code == 404