From a2f34bae4213770eaf7e2d3f6712a54beb250454 Mon Sep 17 00:00:00 2001
From: Maksym Lohvinenko <84383821+mlohvinenko@users.noreply.github.com>
Date: Thu, 25 Sep 2025 19:51:11 +0000
Subject: [PATCH 1/3] Add extracurricular activities and signup validation to
the API
---
src/app.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 68 insertions(+), 5 deletions(-)
diff --git a/src/app.py b/src/app.py
index 4ebb1d9..4a27d4a 100644
--- a/src/app.py
+++ b/src/app.py
@@ -21,23 +21,82 @@
# In-memory activity database
activities = {
+ # Sports-related 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"]
},
+ "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"]
}
}
@@ -62,6 +121,10 @@ def signup_for_activity(activity_name: str, email: str):
# Get the specific activity
activity = activities[activity_name]
+ # Validate student is not 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}"}
From 8fa0d1dbc587482d3dca63a1fc242c05a6b50593 Mon Sep 17 00:00:00 2001
From: Maksym Lohvinenko <84383821+mlohvinenko@users.noreply.github.com>
Date: Thu, 25 Sep 2025 21:17:02 +0000
Subject: [PATCH 2/3] Add participants list display and styling to activity
cards
---
src/app.py | 2 +-
src/static/app.js | 15 +++++++++++++++
src/static/styles.css | 35 +++++++++++++++++++++++++++++++++++
3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/src/app.py b/src/app.py
index 4a27d4a..ea7c903 100644
--- a/src/app.py
+++ b/src/app.py
@@ -26,7 +26,7 @@
"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",
diff --git a/src/static/app.js b/src/static/app.js
index dcc1e38..758e930 100644
--- a/src/static/app.js
+++ b/src/static/app.js
@@ -25,6 +25,21 @@ document.addEventListener("DOMContentLoaded", () => {
${details.description}
Schedule: ${details.schedule}
Availability: ${spotsLeft} spots left
+
+
Participants:
+ ${
+ details.participants.length > 0
+ ? `
+ ${details.participants
+ .map(
+ (participant) =>
+ `- ${participant}
`
+ )
+ .join("")}
+
`
+ : `
No participants yet.
`
+ }
+
`;
activitiesList.appendChild(activityCard);
diff --git a/src/static/styles.css b/src/static/styles.css
index a533b32..7981e21 100644
--- a/src/static/styles.css
+++ b/src/static/styles.css
@@ -63,6 +63,7 @@ section h3 {
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
+ position: relative;
}
.activity-card h4 {
@@ -74,6 +75,40 @@ 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;
+}
+
+.participant-item {
+ margin-bottom: 4px;
+ color: #333;
+ font-size: 15px;
+}
+
+.no-participants {
+ color: #888;
+ font-style: italic;
+ margin-left: 2px;
+ margin-bottom: 0;
+}
+
.form-group {
margin-bottom: 15px;
}
From bcd16fc333e884cb55dae8d4f2a8c018a079b370 Mon Sep 17 00:00:00 2001
From: Maksym Lohvinenko <84383821+mlohvinenko@users.noreply.github.com>
Date: Thu, 25 Sep 2025 21:49:28 +0000
Subject: [PATCH 3/3] Implement MongoDB integration for activities management
and add participant removal functionality
---
requirements.txt | 1 +
src/app.py | 47 +++++++++++++++++++++---------
src/init_db.py | 16 +++++++++++
src/static/app.js | 66 +++++++++++++++++++++++++++++--------------
src/static/styles.css | 20 +++++++++++++
5 files changed, 116 insertions(+), 34 deletions(-)
create mode 100644 src/init_db.py
diff --git a/requirements.txt b/requirements.txt
index 97dc7cd..f5dc0e6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
fastapi
uvicorn
+pymongo
\ No newline at end of file
diff --git a/src/app.py b/src/app.py
index ea7c903..a5f931e 100644
--- a/src/app.py
+++ b/src/app.py
@@ -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",
@@ -19,9 +21,13 @@
app.mount("/static", StaticFiles(directory=os.path.join(Path(__file__).parent,
"static")), name="static")
-# In-memory activity database
-activities = {
- # Sports-related 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",
@@ -108,23 +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]
-
- # Validate student is not 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)
+ 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}"}
\ No newline at end of file
diff --git a/src/init_db.py b/src/init_db.py
new file mode 100644
index 0000000..8c3a482
--- /dev/null
+++ b/src/init_db.py
@@ -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!")
\ No newline at end of file
diff --git a/src/static/app.js b/src/static/app.js
index 758e930..559fe18 100644
--- a/src/static/app.js
+++ b/src/static/app.js
@@ -20,27 +20,30 @@ document.addEventListener("DOMContentLoaded", () => {
const spotsLeft = details.max_participants - details.participants.length;
- activityCard.innerHTML = `
- ${name}
- ${details.description}
- Schedule: ${details.schedule}
- Availability: ${spotsLeft} spots left
-
-
Participants:
- ${
- details.participants.length > 0
- ? `
- ${details.participants
- .map(
- (participant) =>
- `- ${participant}
`
- )
- .join("")}
-
`
- : `
No participants yet.
`
- }
-
- `;
+ activityCard.innerHTML = `
+ ${name}
+ ${details.description}
+ Schedule: ${details.schedule}
+ Availability: ${spotsLeft} spots left
+
+
Participants:
+ ${
+ details.participants.length > 0
+ ? `
+ ${details.participants
+ .map(
+ (participant) =>
+ `-
+ ${participant}
+ ✖
+
`
+ )
+ .join("")}
+
`
+ : `
No participants yet.
`
+ }
+
+ `;
activitiesList.appendChild(activityCard);
@@ -50,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 = "Failed to load activities. Please try again later.
";
console.error("Error fetching activities:", error);
@@ -77,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";
diff --git a/src/static/styles.css b/src/static/styles.css
index 7981e21..700563c 100644
--- a/src/static/styles.css
+++ b/src/static/styles.css
@@ -94,6 +94,26 @@ section h3 {
margin-left: 20px;
margin-bottom: 0;
padding-left: 0;
+
+ list-style: none;
+ padding-left: 0;
+}
+
+.participant-item {
+ display: flex;
+ align-items: center;
+}
+
+.delete-icon {
+ cursor: pointer;
+ margin-left: 8px;
+ color: #d00;
+ font-weight: bold;
+ transition: color 0.2s;
+}
+
+.delete-icon:hover {
+ color: #a00;
}
.participant-item {