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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
fastapi
uvicorn
pymongo
116 changes: 100 additions & 16 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse
import os
from pymongo import MongoClient
from pymongo.errors import DuplicateKeyError
from pathlib import Path

app = FastAPI(title="Mergington High School API",
Expand All @@ -19,25 +21,88 @@
app.mount("/static", StaticFiles(directory=os.path.join(Path(__file__).parent,
"static")), name="static")

# In-memory activity database
activities = {
# MongoDB connection
client = MongoClient('mongodb://localhost:27017/')
db = client['school_activities']
activities_collection = db['activities']

# Initial activities data
initial_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"]
"participants": ["michael@mergington.edu", "daniel@mergington.edu", "olivia@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 school soccer team and compete in matches",
"schedule": "Tuesdays, Thursdays, 4:00 PM - 5:30 PM",
"max_participants": 22,
"participants": ["alex@mergington.edu", "lucas@mergington.edu"]
},
"Basketball Club": {
"description": "Practice basketball skills and play friendly games",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 15,
"participants": ["mia@mergington.edu", "noah@mergington.edu"]
},
"Swimming Team": {
"description": "Train and compete in swimming events",
"schedule": "Mondays, 4:00 PM - 5:30 PM",
"max_participants": 20,
"participants": ["ava@mergington.edu", "liam@mergington.edu"]
},

# Artistic activities
"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"]
"Art Club": {
"description": "Explore painting, drawing, and other visual arts",
"schedule": "Fridays, 2:00 PM - 3:30 PM",
"max_participants": 18,
"participants": ["isabella@mergington.edu", "ethan@mergington.edu"]
},
"Drama Society": {
"description": "Act, direct, and produce school plays and performances",
"schedule": "Thursdays, 3:30 PM - 5:00 PM",
"max_participants": 25,
"participants": ["charlotte@mergington.edu", "jack@mergington.edu"]
},
"Music Ensemble": {
"description": "Perform in a group with various musical instruments",
"schedule": "Wednesdays, 4:00 PM - 5:30 PM",
"max_participants": 20,
"participants": ["amelia@mergington.edu", "benjamin@mergington.edu"]
},

# Intellectual activities
"Science Club": {
"description": "Conduct experiments and explore scientific concepts",
"schedule": "Mondays, 3:30 PM - 5:00 PM",
"max_participants": 16,
"participants": ["elijah@mergington.edu", "zoe@mergington.edu"]
},
"Mathletes": {
"description": "Solve challenging math problems and compete in contests",
"schedule": "Tuesdays, 2:00 PM - 3:30 PM",
"max_participants": 12,
"participants": ["logan@mergington.edu", "grace@mergington.edu"]
},
"Debate Team": {
"description": "Develop public speaking and argumentation skills",
"schedule": "Fridays, 4:00 PM - 5:30 PM",
"max_participants": 14,
"participants": ["henry@mergington.edu", "ella@mergington.edu"]
}
}

Expand All @@ -49,19 +114,38 @@ def root():

@app.get("/activities")
def get_activities():
return activities

activities_list = activities_collection.find()
return {activity["_id"]: {k: v for k, v in activity.items() if k != "_id"}
for activity in activities_list}

@app.post("/activities/{activity_name}/signup")
def signup_for_activity(activity_name: str, email: str):
"""Sign up a student for an activity"""
# Validate activity exists
if activity_name not in activities:
activity = activities_collection.find_one({"_id": activity_name})
if not activity:
raise HTTPException(status_code=404, detail="Activity not found")

# Get the specific activity
activity = activities[activity_name]
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up for this activity")

# Add student
activity["participants"].append(email)
activities_collection.update_one(
{"_id": activity_name},
{"$push": {"participants": email}}
)
return {"message": f"Signed up {email} for {activity_name}"}

@app.post("/activities/{activity_name}/unregister")
def unregister_from_activity(activity_name: str, email: str):
"""Unregister a student from an activity"""
activity = activities_collection.find_one({"_id": activity_name})
if not activity:
raise HTTPException(status_code=404, detail="Activity not found")

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

activities_collection.update_one(
{"_id": activity_name},
{"$pull": {"participants": email}}
)
return {"message": f"Unregistered {email} from {activity_name}"}
16 changes: 16 additions & 0 deletions src/init_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pymongo import MongoClient
from app import initial_activities

# Connect to MongoDB
client = MongoClient('mongodb://localhost:27017/')
db = client['school_activities']
activities_collection = db['activities']

# Clear existing data
activities_collection.delete_many({})

# Insert initial activities
for name, details in initial_activities.items():
activities_collection.insert_one({"_id": name, **details})

print("Database initialized successfully!")
51 changes: 45 additions & 6 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,30 @@ document.addEventListener("DOMContentLoaded", () => {

const spotsLeft = details.max_participants - details.participants.length;

activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</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-section">
<strong>Participants:</strong>
${
details.participants.length > 0
? `<ul class="participants-list" style="list-style: none; padding-left: 0;">
${details.participants
.map(
(participant) =>
`<li class="participant-item" style="display: flex; align-items: center;">
<span>${participant}</span>
<span class="delete-icon" title="Remove participant" data-activity="${name}" data-participant="${participant}" style="cursor: pointer; margin-left: 8px; color: #d00; font-weight: bold;">&#10006;</span>
Comment on lines +32 to +38
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline styles are being used when CSS classes already exist for the same properties. Remove the inline styles and rely on the existing CSS classes .participants-list, .participant-item, and .delete-icon to maintain consistency and improve maintainability.

Suggested change
? `<ul class="participants-list" style="list-style: none; padding-left: 0;">
${details.participants
.map(
(participant) =>
`<li class="participant-item" style="display: flex; align-items: center;">
<span>${participant}</span>
<span class="delete-icon" title="Remove participant" data-activity="${name}" data-participant="${participant}" style="cursor: pointer; margin-left: 8px; color: #d00; font-weight: bold;">&#10006;</span>
? `<ul class="participants-list">
${details.participants
.map(
(participant) =>
`<li class="participant-item">
<span>${participant}</span>
<span class="delete-icon" title="Remove participant" data-activity="${name}" data-participant="${participant}">&#10006;</span>

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +38
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline styles are being used when CSS classes already exist for the same properties. Remove the inline styles and rely on the existing CSS classes .participants-list, .participant-item, and .delete-icon to maintain consistency and improve maintainability.

Suggested change
? `<ul class="participants-list" style="list-style: none; padding-left: 0;">
${details.participants
.map(
(participant) =>
`<li class="participant-item" style="display: flex; align-items: center;">
<span>${participant}</span>
<span class="delete-icon" title="Remove participant" data-activity="${name}" data-participant="${participant}" style="cursor: pointer; margin-left: 8px; color: #d00; font-weight: bold;">&#10006;</span>
? `<ul class="participants-list">
${details.participants
.map(
(participant) =>
`<li class="participant-item">
<span>${participant}</span>
<span class="delete-icon" title="Remove participant" data-activity="${name}" data-participant="${participant}">&#10006;</span>

Copilot uses AI. Check for mistakes.
</li>`
)
.join("")}
</ul>`
: `<p class="no-participants">No participants yet.</p>`
}
</div>
`;

activitiesList.appendChild(activityCard);

Expand All @@ -35,6 +53,26 @@ document.addEventListener("DOMContentLoaded", () => {
option.textContent = name;
activitySelect.appendChild(option);
});

// Add event listeners for delete icons
document.querySelectorAll('.delete-icon').forEach(icon => {
icon.addEventListener('click', async (e) => {
const activity = icon.getAttribute('data-activity');
const participant = icon.getAttribute('data-participant');
try {
const response = await fetch(`/activities/${encodeURIComponent(activity)}/unregister?email=${encodeURIComponent(participant)}`, {
method: 'POST',
});
if (response.ok) {
fetchActivities(); // Refresh list
} else {
alert('Failed to remove participant.');
}
} catch (err) {
alert('Error removing participant.');
}
});
});
} catch (error) {
activitiesList.innerHTML = "<p>Failed to load activities. Please try again later.</p>";
console.error("Error fetching activities:", error);
Expand Down Expand Up @@ -62,6 +100,7 @@ document.addEventListener("DOMContentLoaded", () => {
messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
fetchActivities(); // Refresh activities list
} else {
messageDiv.textContent = result.detail || "An error occurred";
messageDiv.className = "error";
Expand Down
55 changes: 55 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ section h3 {
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
position: relative;
}

.activity-card h4 {
Expand All @@ -74,6 +75,60 @@ section h3 {
margin-bottom: 8px;
}

.participants-section {
margin-top: 12px;
padding: 10px;
background-color: #eef2f7;
border-radius: 4px;
border: 1px solid #dde3ec;
}

.participants-section strong {
display: block;
margin-bottom: 6px;
color: #3949ab;
}

.participants-list {
list-style-type: disc;
margin-left: 20px;
margin-bottom: 0;
padding-left: 0;

list-style: none;
padding-left: 0;
}
Comment on lines +92 to +100
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSS properties are contradictory and redundant. Lines 93-94 set list-style-type: disc and margin-left: 20px, but lines 98-99 immediately override them with list-style: none and padding-left: 0. Remove the redundant properties on lines 93-94.

Copilot uses AI. Check for mistakes.

.participant-item {
display: flex;
align-items: center;
}
Comment on lines +102 to +105
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .participant-item selector is duplicated with different properties. These should be combined into a single rule block to avoid confusion and potential specificity issues.

Copilot uses AI. Check for mistakes.

.delete-icon {
cursor: pointer;
margin-left: 8px;
color: #d00;
font-weight: bold;
transition: color 0.2s;
}

.delete-icon:hover {
color: #a00;
}

.participant-item {
margin-bottom: 4px;
color: #333;
font-size: 15px;
}
Comment on lines +119 to +123
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .participant-item selector is duplicated with different properties. These should be combined into a single rule block to avoid confusion and potential specificity issues.

Copilot uses AI. Check for mistakes.

.no-participants {
color: #888;
font-style: italic;
margin-left: 2px;
margin-bottom: 0;
}

.form-group {
margin-bottom: 15px;
}
Expand Down