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 4ebb1d9..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,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"] } } @@ -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}"} \ 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 dcc1e38..559fe18 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -20,12 +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

- `; + activityCard.innerHTML = ` +

${name}

+

${details.description}

+

Schedule: ${details.schedule}

+

Availability: ${spotsLeft} spots left

+
+ Participants: + ${ + details.participants.length > 0 + ? `` + : `

No participants yet.

` + } +
+ `; activitiesList.appendChild(activityCard); @@ -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 = "

Failed to load activities. Please try again later.

"; console.error("Error fetching activities:", error); @@ -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"; diff --git a/src/static/styles.css b/src/static/styles.css index a533b32..700563c 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,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; +} + +.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 { + 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; }