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
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
fastapi
uvicorn
pytest
httpx
64 changes: 64 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,48 @@
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
},

# Sports-related (2 more)
"Soccer Team": {
"description": "Competitive soccer team for students of all skill levels",
"schedule": "Mondays and Thursdays, 4:00 PM - 6:00 PM",
"max_participants": 25,
"participants": ["liam@mergington.edu", "noah@mergington.edu"]
},
"Basketball Club": {
"description": "Pickup games and skills training for basketball enthusiasts",
"schedule": "Wednesdays, 5:00 PM - 7:00 PM",
"max_participants": 18,
"participants": ["lucas@mergington.edu", "mason@mergington.edu"]
},

# Artistic (2 more)
"Art Club": {
"description": "Explore drawing, painting, and mixed media projects",
"schedule": "Tuesdays, 3:30 PM - 5:00 PM",
"max_participants": 20,
"participants": ["ava@mergington.edu"]
},
"Drama Club": {
"description": "Acting, play production, and stagecraft",
"schedule": "Thursdays, 4:00 PM - 6:00 PM",
"max_participants": 25,
"participants": ["isabella@mergington.edu"]
},

# Intellectual (2 more)
"Math Club": {
"description": "Problem solving, competitions, and math enrichment",
"schedule": "Wednesdays, 3:30 PM - 4:30 PM",
"max_participants": 20,
"participants": ["ethan@mergington.edu"]
},
"Science Olympiad": {
"description": "Prepare for science competitions and run experiments",
"schedule": "Fridays, 3:30 PM - 5:00 PM",
"max_participants": 22,
"participants": ["mia@mergington.edu"]
}
}

Expand All @@ -63,5 +105,27 @@ def signup_for_activity(activity_name: str, email: str):
activity = activities[activity_name]

# Add student
# Check if already signed up
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up for this activity")

activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}


@app.delete("/activities/{activity_name}/participants/{email}")
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 signed up
if email not in activity["participants"]:
raise HTTPException(status_code=400, detail="Student not signed up for this activity")

activity["participants"].remove(email)
return {"message": f"Unregistered {email} from {activity_name}"}
36 changes: 36 additions & 0 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ document.addEventListener("DOMContentLoaded", () => {
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
<p><strong>Participants:</strong></p>
<ul style="list-style-type: none;">
${details.participants.map(participant => `<li>${participant} <button class="delete-btn" data-activity="${name}" data-email="${participant}">×</button></li>`).join('')}
</ul>
`;

activitiesList.appendChild(activityCard);
Expand All @@ -41,6 +45,36 @@ document.addEventListener("DOMContentLoaded", () => {
}
}

// Event listener for delete buttons
activitiesList.addEventListener('click', async (event) => {
if (event.target.classList.contains('delete-btn')) {
const activity = event.target.dataset.activity;
const email = event.target.dataset.email;

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

if (response.ok) {
// Refresh the activities
fetchActivities();
} else {
const result = await response.json();
messageDiv.textContent = result.detail || "Failed to unregister";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
setTimeout(() => messageDiv.classList.add("hidden"), 5000);
}
} catch (error) {
messageDiv.textContent = "Failed to unregister. Please try again.";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
console.error("Error unregistering:", error);
}
}
});

// Handle form submission
signupForm.addEventListener("submit", async (event) => {
event.preventDefault();
Expand All @@ -62,6 +96,8 @@ document.addEventListener("DOMContentLoaded", () => {
messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
// Refresh the activities list
fetchActivities();
} else {
messageDiv.textContent = result.detail || "An error occurred";
messageDiv.className = "error";
Expand Down
11 changes: 11 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ section h3 {
margin-bottom: 8px;
}

.activity-card ul {
list-style-type: disc;
padding-left: 20px;
margin-top: 10px;
color: #555;
}

.activity-card li {
margin-bottom: 5px;
}

.form-group {
margin-bottom: 15px;
}
Expand Down
73 changes: 73 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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
assert "participants" in data["Chess Club"]

def test_signup_success():
"""新しい参加者がアクティビティに正常にサインアップできることを確認するテスト"""
# Test signing up for an activity
response = client.post("/activities/Chess%20Club/signup?email=test@example.com")
assert response.status_code == 200
data = response.json()
assert "Signed up" in data["message"]

# Check if added
response = client.get("/activities")
data = response.json()
assert "test@example.com" in data["Chess Club"]["participants"]

def test_signup_already_signed_up():
"""既にサインアップ済みの参加者が再度サインアップしようとするとエラーになることを確認するテスト"""
# First signup
client.post("/activities/Chess%20Club/signup?email=duplicate@example.com")
# Second signup should fail
response = client.post("/activities/Chess%20Club/signup?email=duplicate@example.com")
assert response.status_code == 400
data = response.json()
assert "already signed up" in data["detail"]

def test_signup_activity_not_found():
"""存在しないアクティビティにサインアップしようとするとエラーになることを確認するテスト"""
response = client.post("/activities/Nonexistent/signup?email=test@example.com")
assert response.status_code == 404
data = response.json()
assert "Activity not found" in data["detail"]

def test_unregister_success():
"""サインアップ済みの参加者がアクティビティから正常にアンサインアップできることを確認するテスト"""
# First signup
client.post("/activities/Programming%20Class/signup?email=unregister@example.com")
# Then unregister
response = client.delete("/activities/Programming%20Class/participants/unregister@example.com")
assert response.status_code == 200
data = response.json()
assert "Unregistered" in data["message"]

# Check if removed
response = client.get("/activities")
data = response.json()
assert "unregister@example.com" not in data["Programming Class"]["participants"]

def test_unregister_not_signed_up():
"""サインアップしていない参加者をアンサインアップしようとするとエラーになることを確認するテスト"""
response = client.delete("/activities/Chess%20Club/participants/notsigned@example.com")
assert response.status_code == 400
data = response.json()
assert "not signed up" in data["detail"]

def test_unregister_activity_not_found():
"""存在しないアクティビティからアンサインアップしようとするとエラーになることを確認するテスト"""
response = client.delete("/activities/Nonexistent/participants/test@example.com")
assert response.status_code == 404
data = response.json()
assert "Activity not found" in data["detail"]