🏡 index : github.com/captn3m0/skills-introduction-to-repository-management.git

author Nemo <me@captnemo.in> 2025-05-09 17:49:38.0 +05:30:00
committer GitHub <noreply@github.com> 2025-05-09 17:49:38.0 +05:30:00
commit
f0da614a2429c12f7d55b8647d49f4d6c55adec3 [patch]
tree
127df13ba7ab1002289312aeaa88609887a50045
download
f0da614a2429c12f7d55b8647d49f4d6c55adec3.tar.gz

Initial commit



Diff

 LICENSE                                            |  21 +++++++++++++++++++++
 README.md                                          |  54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 requirements.txt                                   |   4 ++++
 .devcontainer/devcontainer.json                    |  16 ++++++++++++++++
 .devcontainer/installMongoDB.sh                    |  21 +++++++++++++++++++++
 .devcontainer/postCreate.sh                        |   5 +++++
 .vscode/launch.json                                |  19 +++++++++++++++++++
 src/README.md                                      |  50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/app.py                                         |  35 +++++++++++++++++++++++++++++++++++
 .github/steps/1-protect-your-code.md               | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/steps/2-prepare-to-collaborate.md          | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/steps/3-foster-healthy-growth.md           | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/steps/4-prepare-for-the-inevitable.md      | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/steps/5-merge.md                           |  13 +++++++++++++
 .github/steps/x-review.md                          |  36 ++++++++++++++++++++++++++++++++++++
 .github/workflows/0-start-exercise.yml             |  79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/workflows/1-protect-your-code.yml          | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/workflows/2-prepare-to-collaborate.yml     | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/workflows/3-foster-healthy-growth.yml      | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/workflows/4-prepare-for-the-inevitable.yml | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 .github/workflows/5-merge.yml                      | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/__init__.py                            |   2 ++
 src/backend/database.py                            | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/static/app.js                                  | 868 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/static/index.html                              | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/static/styles.css                              | 666 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/routers/__init__.py                    |   2 ++
 src/backend/routers/activities.py                  | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/routers/auth.py                        |  51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 29 files changed, 3845 insertions(+)

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4506552 100644
--- /dev/null
+++ a/LICENSE
@@ -1,0 +1,21 @@
MIT License

Copyright (c) 2025 GitHub Skills

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e3e9365 100644
--- /dev/null
+++ a/README.md
@@ -1,0 +1,54 @@
# Introduction to Repository Management

_Learn the basics of several GitHub features that can help support a collaborative, friendly, and healthy project._

## Welcome

- **Who is this for**: Developers with the need to start collaborating.
- **What you'll learn**: The different ways to protect your repository's content as more people join as collaborators.
- **What you'll build**: You will prepare Mergington High School's extracurricular activities website repository so additional teachers can safely collaborate.
- **Prerequisites**:
  - Skills exercise: [Introduction to GitHub](https://github.com/skills/introduction-to-github)
  - Skills exercise: [Communicate using Markdown](https://github.com/skills/communicate-using-markdown)
  - Skills exercise: [Review pull requests](https://github.com/skills/review-pull-requests)
- **How long**: This exercise takes less than one hour to complete.

In this exercise, you will:

1. Add a simple rulesets and configuration to restrict repository content.
1. Communicate procedures to help guide collaborators.
1. Assign responsibility of parts of the code to particular collaborators.
1. Learn the difference between collaboration in a personal repository and organization repository.
1. Establish ground rules to promote a health collaboration environment.
1. Establish a process for managing security updates.

> [!IMPORTANT]

> This exercise is meant to provide an overview of many GitHub features.

> It will provide references to learn more but not a detailed explanation for any specific subject.


### How to start this exercise

Simply copy the exercise to your account, then give your favorite Octocat (Mona) **about 20 seconds** to prepare the first lesson, then **refresh the page**.

[![](https://img.shields.io/badge/Copy%20Exercise-%E2%86%92-1f883d?style=for-the-badge&logo=github&labelColor=197935)](https://github.com/new?template_owner=skills&template_name=introduction-to-repository-management&owner=%40me&name=skills-introduction-to-repository-management&description=Exercise:+introduction+to+repository+management&visibility=public)

<details>
<summary>Having trouble? 🤷</summary><br/>

When copying the exercise, we recommend the following settings:

- For owner, choose your personal account or an organization to host the repository.

- We recommend creating a public repository, since private repositories will use Actions minutes.

If the exercise isn't ready in 20 seconds, please check the [Actions](../../actions) tab.

- Check to see if a job is running. Sometimes it simply takes a bit longer.

- If the page shows a failed job, please submit an issue. Nice, you found a bug! 🐛

</details>

---


&copy; 2025 GitHub &bull; [Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md) &bull; [MIT License](https://gh.io/mit)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..1de9182 100644
--- /dev/null
+++ a/requirements.txt
@@ -1,0 +1,4 @@
fastapi
uvicorn
pymongo
argon2-cffi==23.1.0
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..da92c9f 100644
--- /dev/null
+++ a/.devcontainer/devcontainer.json
@@ -1,0 +1,16 @@
{
  "name": "Python 3",
  "image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
  "forwardPorts": [8000],
  "postCreateCommand": "bash ./.devcontainer/postCreate.sh",
  "customizations": {
    "vscode": {
      "extensions": [
        "GitHub.copilot",
        "ms-python.python",
        "ms-python.debugpy",
        "mongodb.mongodb-vscode"
      ]

    }

  }

}

diff --git a/.devcontainer/installMongoDB.sh b/.devcontainer/installMongoDB.sh
new file mode 100755
index 0000000..9d7f6b3 100755
--- /dev/null
+++ a/.devcontainer/installMongoDB.sh
@@ -1,0 +1,21 @@
#!/bin/bash

# Install MongoDB
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org

# Create necessary directories and set permissions
sudo mkdir -p /data/db
sudo chown -R mongodb:mongodb /data/db

# Start MongoDB service
sudo mongod --fork --logpath /var/log/mongodb/mongod.log

echo "MongoDB has been installed and started successfully!"
mongod --version

# Run sample MongoDB commands
echo "Current databases:"
mongosh --eval "db.getMongo().getDBNames()"
diff --git a/.devcontainer/postCreate.sh b/.devcontainer/postCreate.sh
new file mode 100644
index 0000000..8b6615e 100644
--- /dev/null
+++ a/.devcontainer/postCreate.sh
@@ -1,0 +1,5 @@
# Prepare python environment
pip install -r requirements.txt

# Prepare MongoDB Dev DB
./.devcontainer/installMongoDB.sh
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..b349f44 100644
--- /dev/null
+++ a/.vscode/launch.json
@@ -1,0 +1,19 @@
{
  // Use IntelliSense to learn about possible attributes.

  // Hover to view descriptions of existing attributes.

  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387

  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch Mergington WebApp",
      "type": "debugpy",
      "request": "launch",
      "module": "uvicorn",
      "args": [
        "src.app:app",
        "--reload"
      ],

      "jinja": true
    }

  ]

}

diff --git a/src/README.md b/src/README.md
new file mode 100644
index 0000000..a90534b 100644
--- /dev/null
+++ a/src/README.md
@@ -1,0 +1,50 @@
# Mergington High School Activities API

A super simple FastAPI application that allows students to view and sign up for extracurricular activities.

## Features

- View all available extracurricular activities
- Sign up for activities

## Getting Started

1. Install the dependencies:

   ```

   pip install fastapi uvicorn
   ```


2. Run the application:

   ```

   python app.py
   ```


3. Open your browser and go to:
   - API documentation: http://localhost:8000/docs
   - Alternative documentation: http://localhost:8000/redoc

## API Endpoints

| Method | Endpoint                                                          | Description                                                         |
| ------ | ----------------------------------------------------------------- | ------------------------------------------------------------------- |
| GET    | `/activities`                                                     | Get all activities with their details and current participant count |
| POST   | `/activities/{activity_name}/signup?email=student@mergington.edu` | Sign up for an activity                                             |

## Data Model

The application uses a simple data model with meaningful identifiers:

1. **Activities** - Uses activity name as identifier:

   - Description
   - Schedule
   - Maximum number of participants allowed
   - List of student emails who are signed up

2. **Students** - Uses email as identifier:
   - Name
   - Grade level

All data is stored in memory, which means data will be reset when the server restarts.
diff --git a/src/app.py b/src/app.py
new file mode 100644
index 0000000..52cd258 100644
--- /dev/null
+++ a/src/app.py
@@ -1,0 +1,35 @@
"""
High School Management System API

A super simple FastAPI application that allows students to view and sign up
for extracurricular activities at Mergington High School.
"""

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse
import os
from pathlib import Path
from .backend import routers, database

# Initialize web host
app = FastAPI(
    title="Mergington High School API",
    description="API for viewing and signing up for extracurricular activities"
)

# Initialize database with sample data if empty
database.init_database()

# Mount the static files directory for serving the frontend
current_dir = Path(__file__).parent
app.mount("/static", StaticFiles(directory=os.path.join(current_dir, "static")), name="static")

# Root endpoint to redirect to static index.html
@app.get("/")
def root():
    return RedirectResponse(url="/static/index.html")

# Include routers
app.include_router(routers.activities.router)
app.include_router(routers.auth.router)
diff --git a/.github/steps/1-protect-your-code.md b/.github/steps/1-protect-your-code.md
new file mode 100644
index 0000000..6fa36ae 100644
--- /dev/null
+++ a/.github/steps/1-protect-your-code.md
@@ -1,0 +1,173 @@
# Step 1: Protect your code

It's been a busy month at Mergington High! Your simple website for managing extra-curricular activities has really taken off. What started as a basic sign-up form for a few activities has grown into the go-to place for half the school activities. 📚✨

Principal Martinez was so impressed with your work that they announced at the last staff meeting that ALL clubs should start using the website. While this is exciting, you're a bit nervous - the last thing you want is an accidental change breaking the system right before the big Fall Activities Fair! 😰

When more teachers start helping with the Mergington High activities website, it's important to add some safeguards. Thankfully, GitHub provides several ways to protect your repository:

1. **Repository Rulesets** - These provide safeguards to limit:

   - Pushing code directly to important branches
   - Deleting or renaming branches
   - Force pushing (which can overwrite history)
   - (and much more)

1. **`.gitignore`** - This special file tells Git which files it should NOT track, like:

   - Temporary files that your code creates while running
   - Secret configuration files with sensitive information
   - System files that other developers don't need

> [!TIP]

> Think of these settings like the editorial process of a school yearbook. Various student committees will take photos and write articles, then the yearbook president will make adjustments to make sure everything flows together properly. Finally, a teacher/advisor will sign off that all content is appropriate.


## ⌨️ Activity: (optional) Get to know our extracurricular activities site

<details>
<summary>Show Steps</summary>

In other exercise, we have been developing the Extracurricular activities website. You can follow these steps to start up the development environment and try it out.

> ! **Important:** Opening a development environment and running the application is not necessary to complete this exercise. You can skip this activity if desired.


1. Right-click the below button to open the **Create Codespace** page in a new tab. Use the default configuration.

   [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/{{full_repo_name}}?quickstart=1)

1. Wait some time for the environment to be prepared. It will automatically install all requirements and services.

1. Validate the **GitHub Copilot** and **Python** extensions are installed and enabled.

   <img width="300" alt="copilot extension for VS Code" src="https://github.com/user-attachments/assets/ef1ef984-17fc-4b20-a9a6-65a866def468" /><br/>
   <img width="300" alt="python extension for VS Code" src="https://github.com/user-attachments/assets/3040c0f5-1658-47e2-a439-20504a384f77" />

1. Try running the the application. In the left sidebar, select the **Run and Debug** tab and then press the **Start Debugging** icon.

   <details>
   <summary>📸 Show screenshot</summary><br/>

   <img width="300" alt="run and debug" src="https://github.com/user-attachments/assets/50b27f2a-5eab-4827-9343-ab5bce62357e" />

   </details>

   <details>
   <summary>🤷 Having trouble?</summary><br/>

   If the **Run and Debug** area is empty, try reloading VS Code: Open the command palette (`Ctrl`+`Shift`+`P`) and search for `Developer: Reload Window`.

   <img width="300" alt="empty run and debug panel" src="https://github.com/user-attachments/assets/0dbf1407-3a97-401a-a630-f462697082d6" />

   </details>

1. Use the **Ports** tab to find the webpage address, open it, and verify it is running.

   <details>
   <summary>📸 Show screenshot</summary><br/>

   <img width="350" alt="ports tab" src="https://github.com/user-attachments/assets/8d24d6b5-202d-4109-8174-2f0d1e4d8d44" />

   ![Screenshot of Mergington High School WebApp](https://github.com/user-attachments/assets/5e1e7c1e-1b0e-4378-a5af-a266763e6544)

   </details>

</details>

## ⌨️ Activity: Add a branch ruleset

To get started, let's add some protections so that no one accidentally breaks the club registration system.

1. If necessary, open another tab and navigate to this repository. We will start on the **Settings** tab.

1. In the left navigation, expand the **Rules** area and select **Rulesets**.

1. Click the **New ruleset** dropdown and select **New branch ruleset**.

   <img width="250" alt="image" src="https://github.com/user-attachments/assets/1e9fd519-1421-4d6b-b654-a3fe53a8fb75" />

1. Set the **Ruleset Name** as `Protect main` and change the **Enforcement status** to `Active`.

   <img width="250" alt="image" src="https://github.com/user-attachments/assets/ce30fd34-39b5-4e22-b348-4af61fd05cd1" />

1. Find the the **Targets** section and use the **Add target** dropdown to add 2 entries:

   1. Add the **Include default branch** option to ensure protections aren't bypassed by switching the default branch.

      <img width="250" alt="image" src="https://github.com/user-attachments/assets/217263cc-d5c2-4ac0-b03c-a72494e5c812" />


   1. Use the **include by pattern** option and enter the pattern `main`.

      <img width="250" alt="image" src="https://github.com/user-attachments/assets/968c9ed8-b051-44eb-af42-d99670ad31fd" />


      <img width="250" alt="image" src="https://github.com/user-attachments/assets/ddc52767-d93e-4c9e-a77a-90c3b5c08fb5" />


1. Find the **Rules** section and ensure the following items are checked.

   - [x] Restrict deletions
   - [x] Require a pull request before merging
     - Required approvals: `0`

     - [x] Require review from Code Owners

   - [x] Block force pushes

1. Scroll to the bottom and click the **Create** button to save the ruleset.

## ⌨️ Activity: Create a `.gitignore` file

We know many teachers use different tools, so let's make sure they don't accidentally commit unnecessary files.

1. At the top navigation, return to the **Code** tab ane verify you are on the `main` branch.

1. Above the list of files, click the **Add file** dropdown and select **Create new file**.

   <img width="300" alt="New file button" src="https://github.com/user-attachments/assets/8f3f8da8-1471-485a-9df5-8c03ecba2d8e"/>

1. Enter the file name `.gitignore`. We will ignore the template selector for now and make our own. Copy the below example content into it.

   <img width="350" alt="preview of new file" src="https://github.com/user-attachments/assets/580d1a63-a264-4d44-8901-50ad708b8822"/>

   ```gitignore

   # Python backend for club management
   __pycache__/
   *.py[cod]      # Python compiled files
   *$py.class
   *.so
   .Python
   env/
   .env           # Where database passwords are stored
   venv/          # Virtual environment for testing
   .venv

   # Teacher IDE settings
   .vscode/       # Ms. Rodriguez uses VS Code
   .idea/         # Mr. Chen uses PyCharm

   # Local development & testing
   instance/
   .pytest_cache/
   .coverage      # Test coverage reports
   htmlcov/

   # Staff computer files
   .DS_Store      # For teachers with Macs
   Thumbs.db      # For teachers with Windows
   ```


1. In the top right, select the **Commit changes...** button. Notice that it won't let us commit to the `main` branch! Our ruleset is working! Nice!

   <img width="400" alt="image" src="https://github.com/user-attachments/assets/4e85948d-75c8-4c13-8ddd-4707bf9b0805" />

1. Enter `prepare-to-collaborate` for the branch name then click the **Propose changes** button. You will be forwarded to a new page to start a new pull request.

1. Set the title to `Prepare to collaborate` and click the **Create pull request** button. **Do NOT merge yet**, since we will be adding more collaboration related changes.

1. With the file committed, wait a moment for Mona to check your work, provide feedback, and share the next lesson.

> [!TIP]

> GitHub and the community have built a repository with [sample `.gitignore` files](https://github.com/github/gitignore) for many situations. Make sure to check it out!


<details>
<summary>🤷 Having trouble?</summary><br/>

Make sure you pushed the `.gitignore` file to `prepare-to-collaborate` branch. Exact naming for both matters!

</details>
diff --git a/.github/steps/2-prepare-to-collaborate.md b/.github/steps/2-prepare-to-collaborate.md
new file mode 100644
index 0000000..f61f891 100644
--- /dev/null
+++ a/.github/steps/2-prepare-to-collaborate.md
@@ -1,0 +1,134 @@
# Step 2: Prepare to collaborate

Your simple school website has become quite popular! After showing it at the last staff meeting, Ms. Rodriguez from the Art Club and Mr. Chen from the Chess Club came up to you super excited. They have tons of ideas for new features!

- Ms. Rodriguez wants to add a photo gallery
- Mr. Chen dreams of adding a tournament bracket system for the chess/sports activities! 🎨♟️

While you're thrilled about their enthusiasm, you realize you need to set up some guidelines before letting them start changing code. The last thing you want is conflicting changes breaking the registration system right before spring break!

Opening your project to other teachers at Mergington High means thinking about how everyone will collaborate together without breaking each other's code.

**Collaborators** are the people you have granted write access to the project through repository settings.

- Provide other people permissions to change project files while still protecting repository settings.
- Personal repositories have simple permissions. Organization repositories allow flexible permissions such as read, write, maintain, and admin.

To help with collaboration, GitHub provides two special files:

1. **`CONTRIBUTING.md` file** - A "How to Help" guide. Some example content:

   - How to prepare a developer setup of the extra-curricular activities website.
   - The process for suggesting changes.
   - The project's coding style preference, to keep things consistent.
   - How to ask for help when stuck.

1. **`CODEOWNERS` file** - Assign specific people or teams responsible for a portion of the project.

   - When someone creates a pull request, GitHub will automatically ask the right person to review it.

## ⌨️ Activity: Create a welcoming contribution guide

The IT Club meeting is tomorrow, and you need to prepare for Ms. Rodriguez and Mr. Chen to join the project. Let's start a document to help them contribute effectively.

1. At the top navigation, return to the **Code** tab. Ensure you are on the `prepare-to-collaborate` branch.

   <img width="265" alt="image showing the correct branch" src="https://github.com/user-attachments/assets/bd12d0cc-0920-4158-9e96-e3a8fb994c1a" />

1. In the top directory, create a new file called `CONTRIBUTING.md` (case sensitive).

1. Add a welcoming message.

   ```md

   # Contributing to the Mergington High Extra-Curricular Activities Website

   Thank you for your interest in helping improve our school's website!
   Whether you want to add your club's activities, fix a bug, or suggest
   new features, this guide will help you get started. 🎉
   ```


1. Add instructions to help teachers quickly start developing.

   ```md

   ## Development Setup

   1. Clone the repository to your computer.
   2. Install Python requirements: `pip install -r requirements.txt`.
   3. Run the development server: `python src/app.py`.
   4. Visit http://localhost:8000 in your browser to see the website.

   ## Making Changes

   1. Create a new branch for your changes.
      - Use descriptive names like `art-gallery-feature` or `fix-chess-signup`

   2. Make your changes and test them locally with sample student data.
      - Use the MongoDB extension to preview the included sample date.

   3. Push your branch and create a pull request.
   4. Wait for review and address any feedback.

   ## Code Style

   - Follow PEP 8 for Python code (backend).
   - Use clear, descriptive variable names (student_name, start_time, etc.)
   - Add comments to describe blocks of logic.
   ```


1. Add a section for getting help.

   ```md

   ## Need help or have ideas?

   - Check the open issues first.
     - If your problem is there, add a comment or up-vote.

     - If not there, create a new issue. Be as descriptive as possible.

   - Ask in our weekly IT Club office hours (Thursdays at lunch in Room 203).
   - For other general problems, email the tech team at techclub@mergingtonhigh.example.edu
   ```


1. In the top right, use the **Commit changes...** button and commit your changes directly to `prepare-to-collaborate` branch.

## ⌨️ Activity: Assign code ownership

With others joining the fun, you want to stay involved on anything affecting architecture and core functionality. Let's assign you to the related files.

1. At the top navigation, return to the **Code** tab. Ensure you are on the `prepare-to-collaborate` branch.

1. In the top directory, create a new file called `CODEOWNERS` (case sensitive and no extension).

1. Add the following content:

   ```codeowners

   # Core functionality - changes here should be rare!
   /src/app.py                   @{{ login }}
   /src/backend/database.py      @{{ login }}
   /src/backend/routers/auth.py  @{{ login }}

   # The frontend will need refactored soon to be more object oriented.
   /src/static/   @{{ login }}
   ```


1. In the top right, use the **Commit changes...** button and commit your changes directly to `prepare-to-collaborate` branch.

1. With the files committed, wait a moment for Mona to check your work, provide feedback, and share the next lesson.

## ⌨️ Activity: (Optional) Add your first collaborator

Ready to let your colleague start working on that photo gallery feature? Let's do it!

> [!IMPORTANT]

> This step is optional because it requires another person with a GitHub account to participate.


1. In the top navigation, select the **Settings** tab.

1. In the left navigation, select **Collaborators**.

1. Find the **Manage access** area and click the **Add people** button.

   <img width="350" alt="" src="https://github.com/user-attachments/assets/686c32c6-11c2-4e69-bad1-39062d5b4376" />

1. Enter a friend/colleague's GitHub username or email then press the **Add to repository** button.

   <img width="350" alt="" src="https://github.com/user-attachments/assets/d0eaf344-baf0-4a9c-9291-c11e7a9fdaa3" />

> [!IMPORTANT]

> Personal repositories only have one collaboration role type. A "collaborator" receives **write** permissions but NOT **admin** permissions. If you need finer permissions, consider starting a free [organization](https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/about-organizations) and assigning [repository roles](https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization).

diff --git a/.github/steps/3-foster-healthy-growth.md b/.github/steps/3-foster-healthy-growth.md
new file mode 100644
index 0000000..8c7843e 100644
--- /dev/null
+++ a/.github/steps/3-foster-healthy-growth.md
@@ -1,0 +1,131 @@
# Step 3: Foster healthy growth

With so many eager contributors, Principal Martinez pulled you aside after morning announcements: "Your website is becoming critical school infrastructure! We need to make sure it grows in a healthy way as more teachers join. Can you add some guidelines to keep everything organized?"

As your extra-curricular activities website grows, you'll need more than just technical protections and contribution guides. You'll also have to encourage healthy and constructive communication.

Let's look at a couple ways to do that:

1. **Code of Conduct** - This document sets expectations for how community members should interact. Think of it like the Student Handbook at Mergington High - it outlines respectful behavior, how to report non-technical problems, and consequences for violations.

2. **Issue Templates** - These provide structure when someone reports a problem or suggests a new feature. They can help the community effectively communicate their needs for new features and provide enough information to solve bugs.

## ⌨️ Activity: Set expectations with a Code of Conduct

Let's start by establishing some community guidelines for your growing team of teacher-contributors.

> [!TIP]

> The [Contributor Covenant](https://www.contributor-covenant.org/) is a popular code of conduct used by many projects.


1. At the top navigation, return to the **Code** tab. Ensure you are on the `prepare-to-collaborate` branch.

1. In the top directory, create a new file called `CODE_OF_CONDUCT.md` (case sensitive).

1. Add the following content:

   ```markdown

   # Mergington High School Code of Conduct

   ## Our Pledge

   In the interest of fostering an open and welcoming environment for
   our school community, we as contributors and maintainers pledge to
   make participation in the Extra-Curricular Activities project a
   respectful and harassment-free experience for everyone.

   ## Our Standards

   Examples of behavior that contributes to creating a positive environment include:

   - Using welcoming and inclusive language
   - Being respectful of differing viewpoints and experiences
   - Gracefully accepting constructive criticism
   - Focusing on what is best for the students and the school community
   - Showing empathy towards other community members

   Examples of unacceptable behavior include:

   - The use of inappropriate language or imagery
   - Trolling, insulting comments, and personal attacks
   - Public or private harassment
   - Publishing others' private information without explicit permission
   - Other conduct which could reasonably be considered inappropriate in a school setting

   ## Responsibilities

   Project maintainers are responsible for clarifying the standards of
   acceptable behavior and are expected to take appropriate and fair
   corrective action in response to any instances of unacceptable behavior.

   Project maintainers have the right and responsibility to remove, edit,
   or reject comments, commits, code, issues, and other contributions that
   are not aligned to this Code of Conduct.

   ## Scope

   This Code of Conduct applies both within project spaces and in public spaces
   when an individual is representing the project or the school. Examples of
   representing the project include using an official project email address,
   posting via an official social media account, or acting as an appointed
   representative at an online or offline event.

   ## Enforcement

   Instances of abusive, harassing, or otherwise unacceptable behavior may be
   reported to the IT Club faculty advisor. All complaints will be reviewed and
   investigated promptly and fairly.

   Project maintainers who do not follow or enforce the Code of Conduct in good faith may
   face temporary or permanent repercussions as determined by the school administration.

   ## Attribution

   This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
   version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)
   ```


1. In the top right, use the **Commit changes...** button and commit your changes directly to `prepare-to-collaborate` branch.

## ⌨️ Activity: Communicate easier with issue templates

Now let's create templates so other teachers can report bugs or request features in a standardized way.

> [!TIP]

> You might consider trying the public preview for [issue forms](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms), which provide a friendlier user experience when creating issues.


1. In the top navigation, select the **Settings** tab.

1. Find the **Features** section and verify **Issues** is enabled.

   <img width="350" alt="" src="https://github.com/user-attachments/assets/dafb976b-4b8c-4c5e-8989-04d3e7bbe70d" />

1. Click the **Set up templates** button to enter the issue templates editor.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/bd94af1e-d564-472f-a435-f12fa1bf3b5c" />

1. Click the **Add template** dropdown and select **Bug report**.

   <img width="350" alt="" src="https://github.com/user-attachments/assets/baee263d-b233-4029-b629-9544eacf1e27" />

1. Click the **Preview and edit** button to show the current template. Click the **Edit icon** (pencil) to make the fields editable.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/1c8500f7-10b2-406b-9385-d5b9480e2f71" /><br/>

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/77e312e2-af3c-4015-94f0-b1cf7409cc40" /><br/>

   <img width="700" alt="image" src="https://github.com/user-attachments/assets/c2aecd6e-d021-4149-b088-7cbf883a7e33" />

1. (Optional) Let's keep it simple for our students and fellow teachers. Remove the sections about Desktop and Smartphone details.

1. Repeat the above steps for the "Feature request" template.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/6456e261-fcd8-4845-b1ab-f2c2d5883c77" />

1. With our templates prepared, let's commit them. In the top right, click the **Propose changes** button. Enter a description and set the branch to `add-issue-templates`, then click **Commit changes**. You can ignore the automatically created pull request.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/a00a3740-ce0c-430c-9541-e56b7d9b45d6" />

1. With the files committed, wait a moment for Mona to check your work, provide feedback, and share the next lesson.

> [!TIP]

> Did you notice that you are working in parallel on 2 branches now? That's exactly what working with multiple collaborators is like.

diff --git a/.github/steps/4-prepare-for-the-inevitable.md b/.github/steps/4-prepare-for-the-inevitable.md
new file mode 100644
index 0000000..f2a73c9 100644
--- /dev/null
+++ a/.github/steps/4-prepare-for-the-inevitable.md
@@ -1,0 +1,130 @@
# Step 4: Prepare for the inevitable

As you settle into the teachers' lounge with your coffee, you realize something: With more and more teachers contributing to the code, it's only a matter of time before security vulnerabilities creep in. 😱

Every codebase, no matter how well-maintained, will eventually face security challenges. Let's try to proactively prepare for that day by configuring a few tools GitHub offers:

1. **Dependabot** - Track and create alerts for vulnerabilities found in upstream dependencies used in your project. Automatically create pull requests to upgrade dependencies to safe versions.

1. **Code Scanning** - Analyze your repository's code to find security vulnerabilities and coding errors. Use GitHub Copilot Autofix to automatically suggest fixes for these alerts.

1. **Security Policy and Private vulnerability reporting** - Provide a guide and simple form for security researchers and end users to responsibly report vulnerabilities directly to the repository maintainer. This prevents sensitive issues from being publicly disclosed before they're fixed.

> [!NOTE]

> This is just a quick setup guide. For a more detailed setup of each service, we recommend the related GitHub Skills exercises and/or GitHub documentation.


## ⌨️ Activity: Automate security updates with Dependabot

Let's configure Dependabot to use default settings and automatically combine fixes for open alerts, and create pull requests. This will allow us to stay up to date with very little overhead! Nice!

> [!TIP]

> For a deeper dive, check out the [Secure Repository Supply Chain](https://github.com/skills/secure-repository-supply-chain) Skills exercise!


1. In the top navigation, select the **Settings** tab.

1. In the left navigation, select **Advanced Security**.

1. Find the **Dependabot** section. Verify or change the settings to match the following:

   - **Dependabot alerts**: `enabled`
   - **Dependabot security updates**: `enabled`
   - **Grouped security updates**: `enabled`
   - **Dependabot on Actions runners**: `enabled`

1. Find **Dependabot version updates** and click the **Enable** button. This will open an editor to create a configuration file.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/a4d7ae19-0439-4b78-bcbf-9fce5c5410ff" />

1. In the left files list, at the top, click the **Expand file tree** button to show the list of files. At the top, change the branch to `prepare-to-collaborate`. Remember, our ruleset won't let us directly change files on `main`.

   <img width="500" alt="image" src="https://github.com/user-attachments/assets/18a3cd1a-75ab-4e5e-a4c4-efd175d91ced" />

1. Set the `package-ecosytem` to `pip` so Dependabot will automatically monitor our Python requirements.

   <img width="500" alt="image" src="https://github.com/user-attachments/assets/0bc90e67-4b71-4780-8272-20dc0fff5c4c" />

1. In the top right, use the **Commit changes...** button and commit your changes directly to `prepare-to-collaborate` branch.

## ⌨️ Activity: Detect dangerous patterns with code scanning

None of us at the high school are professional software developers. Let's enable code scanning to alert us if we are potentially doing something unsafe. And, let's configure GitHub Copilot to create pull requests with solutions.

> [!TIP]

> Want to learn more about code scanning and writing custom queries? Check out the [Introduction to CodeQL](https://github.com/skills/introduction-to-codeql) Skills exercise after you finish this one!


1. In the top navigation, select the **Settings** tab.

1. In the left navigation, select **Advanced Security**.

1. Find the **Code scanning** section. Click the **Set up** button and select the **Default** option to open a configuration panel.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/5b3a89e5-c71a-44d9-b917-d1a21dc52181" />

1. Click the **Enable CodeQL** button to accept the default configuration.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/6d5f7164-d8ed-4b5d-bbcf-8aed9e7acc5d" />

1. Below the **Tools** section. Verify **Copilot Autofix** is set to `On`.

   <img width="350" alt="image" src="https://github.com/user-attachments/assets/b9d57e7a-f392-4c51-b137-f205a77adb79" />

## ⌨️ Activity: Provide a safe path for security findings

Now that the automated options are ready, let's create a guide for real-life humans to report any security vulnerabilities they find in a safe way.

1. In the top navigation, select the **Settings** tab.

1. In the left navigation, select **Advanced Security**.

1. Find the **Private vulnerability reporting** setting and verify it is `enabled`.

1. At the top navigation, click the **Security** tab.

1. In the left navigation, click the **Policy** option.

1. Click the **Start setup** button. An editor will be started to create the file `SECURITY.md`.

   <img width="350" alt="" src="https://github.com/user-attachments/assets/183b9fcc-1521-47fd-8165-b476a8ccb370"/><br/>

   <img width="350" alt="" src="https://github.com/user-attachments/assets/36c272d1-bc4a-48c8-b234-56173a214cdb"/>

1. In the left files list, at the top, click the **Expand file tree** button to show the list of files. At the top, change the branch to `prepare-to-collaborate`. Remember, our ruleset won't let us directly change files on `main`.

1. We will ignore the provided template and instead use a recommendation from Mergington High School's IT department. Add the following content:

   > 💡**Tip** If you switch to a branch that does not contain the same file, the editor will become empty. Press the **Restore** button to retrieve the previous editor's content.


   ```markdown

   # Mergington High School Security Policy

   ## Reporting a Vulnerability

   At Mergington High, we take the security of our Extra-Curricular Activities website seriously, especially
   since it contains student information. If you discover a security vulnerability, please follow these steps:

   1. **Do not** create an issue on this repository, disclose the vulnerability publicly, or discuss it with other teachers/students.
   1. In the top navigation of this repository, click the **Security** tab.
   1. In the top right, click the **Report a vulnerability** button.
   1. Fill out the provided form. It will request information like:
      - A description of the vulnerability

      - Steps to reproduce the issue

      - Potential impact on student data or website functionality

      - Suggested fix (if you have one)

   1. Email the IT Club faculty advisor at techsupport@mergingtonhigh.example.edu and inform them you have made a report. **Do not** include any vulnerability details.

   ## Response Timeline

   - We will acknowledge receipt of your report within 2 school days
   - We will provide an initial assessment within 5 school days
   - Critical issues affecting student data will be addressed immediately
   - We will create a private fork to solve the issue and invite you as a collaborator so you can see our progress and contribute.

   ## Thank You

   Your help in keeping our school's digital resources secure is greatly appreciated!
   Responsible disclosure of security vulnerabilities helps protect our entire school community.
   ```


1. In the top right, use the **Commit changes...** button and commit your changes directly to `prepare-to-collaborate` branch.

1. With the files committed, wait a moment for Mona to check your work, provide feedback, and share the next lesson.
diff --git a/.github/steps/5-merge.md b/.github/steps/5-merge.md
new file mode 100644
index 0000000..959d770 100644
--- /dev/null
+++ a/.github/steps/5-merge.md
@@ -1,0 +1,13 @@
# Step 5: Release

With all our preparations ready, it's time to release them!

## ⌨️ Activity: Merge our collaboration changes

1. In the top navigation, select the **Pull requests** tab.

1. Find the pull request for the `prepare-to-collaborate` branch and merge it. You may need to wait for your new security scans to finish.

1. Find the pull request for the `add-issue-templates` branch and merge it. You may need to wait for your new security scans to finish.

1. With both pull requests merged, Mona will prepare the final review and acknowledge the exercise as finished! Nice work! You are all done! 🎉
diff --git a/.github/steps/x-review.md b/.github/steps/x-review.md
new file mode 100644
index 0000000..a0250c2 100644
--- /dev/null
+++ a/.github/steps/x-review.md
@@ -1,0 +1,36 @@
## Review

_Congratulations, you've completed this exercise! You're all set for an awesome time collaborating with your fellow teachers!_

<img src="https://octodex.github.com/images/jetpacktocat.png" alt=celebrate width=150 align=right>

You've successfully prepared Mergington High's extracurricular activities website for healthy and safe collaboration. Make sure you let the principal know so he can brag to the IT department about your proactive efforts!

Here is a snippet of something you can share:

- Protected our code from accidental mistakes with `.gitignore` and branch protections.
- Set clear guidelines for teacher contributions with `CONTRIBUTING.md` and `CODEOWNERS`.
- Established community standards with a Code of Conduct and structured issue templates.
- Prepared for the future security challenges by enabling automated scanning and providing safe submission procedures.

### What's next?

This exercise was meant to introduce you to many of the different areas of managing a repository. However, there is still more to learn!

Here are some additional exercises for a deeper dive:

- [Skills: Secure your repository supply chain](https://github.com/skills/secure-repository-supply-chain)
- [Skills: Introduction to CodeQL](https://github.com/skills/introduction-to-codeql)
- [Skills: Introduction to Secret Scanning](https://github.com/skills/introduction-to-secret-scanning)

Here are some useful references from the [GitHub Docs](https://docs.github.com/en):

- [How to ignore files](https://docs.github.com/en/get-started/git-basics/ignoring-files)
- [Template gitignore files](https://github.com/github/gitignore)
- [Creating rulesets for a repository](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#using-fnmatch-syntax)
- [Managing a branch protection rule](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule)
- [About code owners](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners)
- [Setting guidelines for contributors](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors)
- [Add a code of conduct](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-code-of-conduct-to-your-project)
- [Configuring default setup for code scanning](https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning)
- [Adding a security policy](https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository)
diff --git a/.github/workflows/0-start-exercise.yml b/.github/workflows/0-start-exercise.yml
new file mode 100644
index 0000000..5fc2eca 100644
--- /dev/null
+++ a/.github/workflows/0-start-exercise.yml
@@ -1,0 +1,79 @@
name: Step 0 # Start Exercise

on:
  push:
    branches:
      - main

permissions:
  contents: write
  actions: write
  issues: write

env:
  STEP_1_FILE: ".github/steps/1-protect-your-code.md"

jobs:
  start_exercise:
    if: |

      !github.event.repository.is_template
    name: Start Exercise
    uses: skills/exercise-toolkit/.github/workflows/start-exercise.yml@v0.3.0
    with:
      exercise-title: "Introduction to Repository Management"
      intro-message: "In this exercise, you'll configure your repository for easier collaboration. You'll learn how to protect your code, prepare for collaboration, and foster healthy growth in your projects."

  post_next_step_content:
    name: Post next step content
    runs-on: ubuntu-latest
    needs: [start_exercise]
    env:
      ISSUE_URL: ${{ needs.start_exercise.outputs.issue-url }}

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Load Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Configure Git user
        run: |

          git config user.name github-actions[bot]
          git config user.email github-actions[bot]@users.noreply.github.com

      - name: Build comment - add step content
        id: build-comment
        uses: skills/action-text-variables@v2
        with:
          template-file: ${{ env.STEP_1_FILE }}
          template-vars: |

            login: ${{ github.actor }}
            full_repo_name: ${{ github.repository }}

      - name: Create comment - add step content
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$ISSUE_BODY"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_BODY: ${{ steps.build-comment.outputs.updated-text }}

      - name: Create comment - watching for progress
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file "exercise-toolkit/markdown-templates/step-feedback/watching-for-progress.md"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Disable current workflow and enable next one
        run: |

          # gh workflow enable "Step 0" # Already disabled
          gh workflow enable "Step 1" || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/1-protect-your-code.yml b/.github/workflows/1-protect-your-code.yml
new file mode 100644
index 0000000..092599e 100644
--- /dev/null
+++ a/.github/workflows/1-protect-your-code.yml
@@ -1,0 +1,112 @@
name: Step 1

on:
  push:
    branches:
      - prepare-to-collaborate
    paths:
      - ".gitignore"

permissions:
  contents: write
  actions: write
  issues: write

env:
  STEP_2_FILE: ".github/steps/2-prepare-to-collaborate.md"

jobs:
  find_exercise:
    if: |

      !github.event.repository.is_template
    name: Find Exercise Issue
    uses: skills/exercise-toolkit/.github/workflows/find-exercise-issue.yml@v0.3.0

  check_step_work:
    name: Check step work
    runs-on: ubuntu-latest
    needs: [find_exercise]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Load Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      # START: Check practical exercise

      # Creating the new .gitignore file is enough to finish this step.

      # END: Check practical exercise

      - name: Build message - step finished
        id: build-message-step-finish
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-finished-prepare-next-step.md
          template-vars: |

            next_step_number: 2

      - name: Update comment - step finished
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$ISSUE_BODY" \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_BODY: ${{ steps.build-message-step-finish.outputs.updated-text }}

  post_next_step_content:
    name: Post next step content
    runs-on: ubuntu-latest
    needs: [find_exercise, check_step_work]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Build message - add step content
        id: build-message-add-step-content
        uses: skills/action-text-variables@v2
        with:
          template-file: ${{ env.STEP_2_FILE }}
          template-vars: |

            login: ${{ github.actor }}

      - name: Create comment - step results
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$COMMENT_BODY"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COMMENT_BODY: ${{ steps.build-message-add-step-content.outputs.updated-text }}
      
      - name: Create comment - watching for progress
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/watching-for-progress.md
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Disable current workflow and enable next one
        run: |

          gh workflow disable "Step 1" || true
          gh workflow enable "Step 2" || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/2-prepare-to-collaborate.yml b/.github/workflows/2-prepare-to-collaborate.yml
new file mode 100644
index 0000000..758c726 100644
--- /dev/null
+++ a/.github/workflows/2-prepare-to-collaborate.yml
@@ -1,0 +1,197 @@
name: Step 2

on:
  push:
    branches:
      - prepare-to-collaborate
    paths:
      - "CONTRIBUTING.md"
      - "CODEOWNERS"

permissions:
  contents: read
  actions: write
  issues: write

env:
  STEP_3_FILE: ".github/steps/3-foster-healthy-growth.md"

jobs:
  find_exercise:
    if: |

      !github.event.repository.is_template
    name: Find Exercise Issue
    uses: skills/exercise-toolkit/.github/workflows/find-exercise-issue.yml@v0.3.0

  check_step_work:
    name: Check step work
    runs-on: ubuntu-latest
    needs: [find_exercise]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Update comment - checking work
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/checking-work.md \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # START: Check practical exercise

      - name: Check CONTRIBUTING.md exists
        id: check-contributing
        uses: actions/github-script@v7
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'CONTRIBUTING.md',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('CONTRIBUTING.md')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check CODEOWNERS exists
        id: check-codeowners
        uses: actions/github-script@v7
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'CODEOWNERS',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('CODEOWNERS')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check all results
        id: check-all-results
        uses: actions/github-script@v7
        with:
          script: |

            const checks = [
              JSON.parse(process.env['check1']),
              JSON.parse(process.env['check2']),
            ];
  
            const result = checks.every(check => check.passed);
            return result
        env:
          check1: ${{ steps.check-contributing.outputs.result }}
          check2: ${{ steps.check-codeowners.outputs.result }}

      - name: Build message - step results
        id: build-message-step-results
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-results.md
          template-vars: >

            {
              "step_number": 3,
              "passed": ${{ steps.check-all-results.outputs.result }},
              "results_table": [
                  ${{ steps.check-contributing.outputs.result }},
                  ${{ steps.check-codeowners.outputs.result }}
                ]
            }

      - name: Create comment - step results
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$COMMENT_BODY" \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COMMENT_BODY: ${{ steps.build-message-step-results.outputs.updated-text }}

      - name: Fail job if not all checks passed
        if: steps.check-all-results.outputs.result == 'false'
        run: exit 1

      # END: Check practical exercise

      - name: Build message - step finished
        id: build-message-step-finish
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-finished-prepare-next-step.md
          template-vars: |

            next_step_number: 3

      - name: Update comment - step finished
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$ISSUE_BODY"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_BODY: ${{ steps.build-message-step-finish.outputs.updated-text }}

  post_next_step_content:
    name: Post next step content
    runs-on: ubuntu-latest
    needs: [find_exercise, check_step_work]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Create comment - add step content
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file "$STEP_3_FILE"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create comment - watching for progress
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/watching-for-progress.md
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Disable current workflow and enable next one
        run: |

          gh workflow disable "Step 2" || true
          gh workflow enable "Step 3" || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/3-foster-healthy-growth.yml b/.github/workflows/3-foster-healthy-growth.yml
new file mode 100644
index 0000000..33435a8 100644
--- /dev/null
+++ a/.github/workflows/3-foster-healthy-growth.yml
@@ -1,0 +1,233 @@
name: Step 3

on:
  push:
    branches:
      - add-issue-templates
      - prepare-to-collaborate
    paths:
      - ".github/ISSUE_TEMPLATE/**"
      - "CODE_OF_CONDUCT.md"

permissions:
  contents: read
  actions: write
  issues: write

env:
  STEP_4_FILE: ".github/steps/4-prepare-for-the-inevitable.md"

jobs:
  find_exercise:
    if: |

      !github.event.repository.is_template
    name: Find Exercise Issue
    uses: skills/exercise-toolkit/.github/workflows/find-exercise-issue.yml@v0.3.0

  check_step_work:
    name: Check step work
    runs-on: ubuntu-latest
    needs: [find_exercise]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout branch - prepare-to-collaborate
        uses: actions/checkout@v4
        with:
          path: prepare-to-collaborate
          ref: prepare-to-collaborate

      - name: Checkout branch - add-issue-templates
        uses: actions/checkout@v4
        with:
          path: add-issue-templates
          ref: add-issue-templates

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Update comment - checking work
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/checking-work.md \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # START: Check practical exercise

      - name: Check for Code of Conduct
        id: check-code-of-conduct
        uses: actions/github-script@v7
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'CODE_OF_CONDUCT.md',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('prepare-to-collaborate/CODE_OF_CONDUCT.md')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check for Bug Report Templates
        id: check-bug-report-template
        uses: actions/github-script@v7
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'bug_report.md',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('add-issue-templates/.github/ISSUE_TEMPLATE/bug_report.md')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check for Feature Request Templates
        id: check-feature-request-template
        uses: actions/github-script@v7
        continue-on-error: true
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'feature_request.md',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('add-issue-templates/.github/ISSUE_TEMPLATE/feature_request.md')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check all results
        id: check-all-results
        uses: actions/github-script@v7
        with:
          script: |

            const checks = [
              JSON.parse(process.env['check1']),
              JSON.parse(process.env['check2']),
              JSON.parse(process.env['check3'])
            ];

            const result = checks.every(check => check.passed);
            return result
        env:
          check1: ${{ steps.check-code-of-conduct.outputs.result }}
          check2: ${{ steps.check-bug-report-template.outputs.result }}
          check3: ${{ steps.check-feature-request-template.outputs.result }}

      - name: Build message - step results
        id: build-message-step-results
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-results.md
          template-vars: >

            {
              "step_number": 4,
              "passed": ${{ steps.check-all-results.outputs.result }},
              "results_table": [
                  ${{ steps.check-code-of-conduct.outputs.result }},
                  ${{ steps.check-bug-report-template.outputs.result }},
                  ${{ steps.check-feature-request-template.outputs.result }}
                ]
            }

      - name: Create comment - step results
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$COMMENT_BODY" \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COMMENT_BODY: ${{ steps.build-message-step-results.outputs.updated-text }}

      - name: Fail job if not all checks passed
        if: steps.check-all-results.outputs.result == 'false'
        run: exit 1

      # END: Check practical exercise

      - name: Build message - step finished
        id: build-message-step-finish
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-finished-prepare-next-step.md
          template-vars: |

            next_step_number: 4

      - name: Update comment - step finished
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$ISSUE_BODY"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_BODY: ${{ steps.build-message-step-finish.outputs.updated-text }}

  post_next_step_content:
    name: Post next step content
    needs: [find_exercise, check_step_work]
    runs-on: ubuntu-latest
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Create comment - add step content
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file "$STEP_4_FILE"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create comment - watching for progress
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/watching-for-progress.md
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Disable current workflow and enable next one
        run: |

          gh workflow disable "Step 3" || true
          gh workflow enable "Step 4" || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/4-prepare-for-the-inevitable.yml b/.github/workflows/4-prepare-for-the-inevitable.yml
new file mode 100644
index 0000000..3704660 100644
--- /dev/null
+++ a/.github/workflows/4-prepare-for-the-inevitable.yml
@@ -1,0 +1,197 @@
name: Step 4

on:
  push:
    branches:
      - prepare-to-collaborate
    paths:
      - ".github/dependabot.yml"
      - "SECURITY.md"

permissions:
  contents: write
  actions: write
  issues: write

env:
  STEP_5_FILE: ".github/steps/5-merge.md"

jobs:
  find_exercise:
    if: |

      !github.event.repository.is_template
    name: Find Exercise Issue
    uses: skills/exercise-toolkit/.github/workflows/find-exercise-issue.yml@v0.3.0

  check_step_work:
    name: Check step work
    runs-on: ubuntu-latest
    needs: [find_exercise]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Update comment - checking work
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/checking-work.md \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # START: Check practical exercise

      - name: Check for Dependabot config
        id: check-dependabot-config
        uses: actions/github-script@v7
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'dependabot.yml',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('.github/dependabot.yml')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check for Security Policy
        id: check-security-policy
        uses: actions/github-script@v7
        with:
          script: |

            const fs = require('fs');

            // Result object to store the message
            let result = {
              name: 'SECURITY.md',
              passed: true,
              message: ''
            }

            // Check that file exists
            if (!fs.existsSync('SECURITY.md')) {
              result.passed = false;
              result.message = 'File is missing.';
            }

            return result;

      - name: Check all results
        id: check-all-results
        uses: actions/github-script@v7
        with:
          script: |

            const checks = [
              JSON.parse(process.env['check1']),
              JSON.parse(process.env['check2'])
            ];

            const result = checks.every(check => check.passed);
            return result
        env:
          check1: ${{ steps.check-dependabot-config.outputs.result }}
          check2: ${{ steps.check-security-policy.outputs.result }}

      - name: Build message - step results
        id: build-message-step-results
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-results.md
          template-vars: >

            {
              "step_number": 4,
              "passed": ${{ steps.check-all-results.outputs.result }},
              "results_table": [
                  ${{ steps.check-dependabot-config.outputs.result }},
                  ${{ steps.check-security-policy.outputs.result }}
                ]
            }

      - name: Create comment - step results
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$COMMENT_BODY" \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COMMENT_BODY: ${{ steps.build-message-step-results.outputs.updated-text }}

      - name: Fail job if not all checks passed
        if: steps.check-all-results.outputs.result == 'false'
        run: exit 1

      # END: Check practical exercise

      - name: Build message - step finished
        id: build-message-step-finish
        uses: skills/action-text-variables@v2
        with:
          template-file: exercise-toolkit/markdown-templates/step-feedback/step-finished-prepare-next-step.md
          template-vars: |

            next_step_number: 5

      - name: Update comment - step finished
        run: |

          gh issue comment "$ISSUE_URL" \
            --body "$ISSUE_BODY"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_BODY: ${{ steps.build-message-step-finish.outputs.updated-text }}

  post_next_step_content:
    name: Post next step content
    needs: [find_exercise, check_step_work]
    runs-on: ubuntu-latest
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Create comment - add step content
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file "$STEP_5_FILE"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create comment - watching for progress
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/watching-for-progress.md
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Disable current workflow and enable next one
        run: |

          gh workflow disable "Step 4" || true
          gh workflow enable "Step 5" || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/5-merge.yml b/.github/workflows/5-merge.yml
new file mode 100644
index 0000000..ba7757a 100644
--- /dev/null
+++ a/.github/workflows/5-merge.yml
@@ -1,0 +1,107 @@
name: Step 5

on:
  pull_request:
    branches:
      - main
    types:
      - closed

concurrency:
  group: ${{ github.workflow }}
  cancel-in-progress: true

permissions:
  contents: write
  actions: write
  issues: write

env:
  REVIEW_FILE: ".github/steps/x-review.md"

jobs:
  find_exercise:
    if: |

      !github.event.repository.is_template
    name: Find Exercise Issue
    uses: skills/exercise-toolkit/.github/workflows/find-exercise-issue.yml@v0.3.0

  check_step_work:
    name: Check step work
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    needs: [find_exercise]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Exercise Toolkit
        uses: actions/checkout@v4
        with:
          repository: skills/exercise-toolkit
          path: exercise-toolkit
          ref: v0.3.0

      - name: Update comment - checking work
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/checking-work.md \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # START: Check practical exercise

      # Merging the pull request is enough to finish this step

      # END: Check practical exercise

      - name: Update comment - step finished - final review next
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file exercise-toolkit/markdown-templates/step-feedback/lesson-review.md \
            --edit-last
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  post_review_content:
    name: Post review content
    runs-on: ubuntu-latest
    needs: [find_exercise, check_step_work]
    env:
      ISSUE_URL: ${{ needs.find_exercise.outputs.issue-url }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Create comment - add review content
        run: |

          gh issue comment "$ISSUE_URL" \
            --body-file ${{ env.REVIEW_FILE }}
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  finish_exercise:
    name: Finish Exercise
    needs: [find_exercise, post_review_content]
    uses: skills/exercise-toolkit/.github/workflows/finish-exercise.yml@57ecd963a3e54e6b01a30ebab57d5202a7afca3f
    with:
      issue-url: ${{ needs.find_exercise.outputs.issue-url }}
      update-readme-with-congratulations: false

  disable_workflow:
    name: Disable this workflow
    needs: [find_exercise, post_review_content]
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Disable current workflow
        run: gh workflow disable "${{github.workflow}}" || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/src/backend/__init__.py b/src/backend/__init__.py
new file mode 100644
index 0000000..826f88a 100644
--- /dev/null
+++ a/src/backend/__init__.py
@@ -1,0 +1,2 @@
from . import routers
from . import database
diff --git a/src/backend/database.py b/src/backend/database.py
new file mode 100644
index 0000000..7c1d056 100644
--- /dev/null
+++ a/src/backend/database.py
@@ -1,0 +1,189 @@
"""
MongoDB database configuration and setup for Mergington High School API
"""

from pymongo import MongoClient
from argon2 import PasswordHasher

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

# Methods
def hash_password(password):
    """Hash password using Argon2"""
    ph = PasswordHasher()
    return ph.hash(password)

def init_database():
    """Initialize database if empty"""

    # Initialize activities if empty
    if activities_collection.count_documents({}) == 0:
        for name, details in initial_activities.items():
            activities_collection.insert_one({"_id": name, **details})
            
    # Initialize teacher accounts if empty
    if teachers_collection.count_documents({}) == 0:
        for teacher in initial_teachers:
            teachers_collection.insert_one({"_id": teacher["username"], **teacher})

# Initial database if empty
initial_activities = {
    "Chess Club": {
        "description": "Learn strategies and compete in chess tournaments",
        "schedule": "Mondays and Fridays, 3:15 PM - 4:45 PM",
        "schedule_details": {
            "days": ["Monday", "Friday"],
            "start_time": "15:15",
            "end_time": "16:45"
        },
        "max_participants": 12,
        "participants": ["michael@mergington.edu", "daniel@mergington.edu"]
    },
    "Programming Class": {
        "description": "Learn programming fundamentals and build software projects",
        "schedule": "Tuesdays and Thursdays, 7:00 AM - 8:00 AM",
        "schedule_details": {
            "days": ["Tuesday", "Thursday"],
            "start_time": "07:00",
            "end_time": "08:00"
        },
        "max_participants": 20,
        "participants": ["emma@mergington.edu", "sophia@mergington.edu"]
    },
    "Morning Fitness": {
        "description": "Early morning physical training and exercises",
        "schedule": "Mondays, Wednesdays, Fridays, 6:30 AM - 7:45 AM",
        "schedule_details": {
            "days": ["Monday", "Wednesday", "Friday"],
            "start_time": "06:30",
            "end_time": "07:45"
        },
        "max_participants": 30,
        "participants": ["john@mergington.edu", "olivia@mergington.edu"]
    },
    "Soccer Team": {
        "description": "Join the school soccer team and compete in matches",
        "schedule": "Tuesdays and Thursdays, 3:30 PM - 5:30 PM",
        "schedule_details": {
            "days": ["Tuesday", "Thursday"],
            "start_time": "15:30",
            "end_time": "17:30"
        },
        "max_participants": 22,
        "participants": ["liam@mergington.edu", "noah@mergington.edu"]
    },
    "Basketball Team": {
        "description": "Practice and compete in basketball tournaments",
        "schedule": "Wednesdays and Fridays, 3:15 PM - 5:00 PM",
        "schedule_details": {
            "days": ["Wednesday", "Friday"],
            "start_time": "15:15",
            "end_time": "17:00"
        },
        "max_participants": 15,
        "participants": ["ava@mergington.edu", "mia@mergington.edu"]
    },
    "Art Club": {
        "description": "Explore various art techniques and create masterpieces",
        "schedule": "Thursdays, 3:15 PM - 5:00 PM",
        "schedule_details": {
            "days": ["Thursday"],
            "start_time": "15:15",
            "end_time": "17:00"
        },
        "max_participants": 15,
        "participants": ["amelia@mergington.edu", "harper@mergington.edu"]
    },
    "Drama Club": {
        "description": "Act, direct, and produce plays and performances",
        "schedule": "Mondays and Wednesdays, 3:30 PM - 5:30 PM",
        "schedule_details": {
            "days": ["Monday", "Wednesday"],
            "start_time": "15:30",
            "end_time": "17:30"
        },
        "max_participants": 20,
        "participants": ["ella@mergington.edu", "scarlett@mergington.edu"]
    },
    "Math Club": {
        "description": "Solve challenging problems and prepare for math competitions",
        "schedule": "Tuesdays, 7:15 AM - 8:00 AM",
        "schedule_details": {
            "days": ["Tuesday"],
            "start_time": "07:15",
            "end_time": "08:00"
        },
        "max_participants": 10,
        "participants": ["james@mergington.edu", "benjamin@mergington.edu"]
    },
    "Debate Team": {
        "description": "Develop public speaking and argumentation skills",
        "schedule": "Fridays, 3:30 PM - 5:30 PM",
        "schedule_details": {
            "days": ["Friday"],
            "start_time": "15:30",
            "end_time": "17:30"
        },
        "max_participants": 12,
        "participants": ["charlotte@mergington.edu", "amelia@mergington.edu"]
    },
    "Weekend Robotics Workshop": {
        "description": "Build and program robots in our state-of-the-art workshop",
        "schedule": "Saturdays, 10:00 AM - 2:00 PM",
        "schedule_details": {
            "days": ["Saturday"],
            "start_time": "10:00",
            "end_time": "14:00"
        },
        "max_participants": 15,
        "participants": ["ethan@mergington.edu", "oliver@mergington.edu"]
    },
    "Science Olympiad": {
        "description": "Weekend science competition preparation for regional and state events",
        "schedule": "Saturdays, 1:00 PM - 4:00 PM",
        "schedule_details": {
            "days": ["Saturday"],
            "start_time": "13:00",
            "end_time": "16:00"
        },
        "max_participants": 18,
        "participants": ["isabella@mergington.edu", "lucas@mergington.edu"]
    },
    "Sunday Chess Tournament": {
        "description": "Weekly tournament for serious chess players with rankings",
        "schedule": "Sundays, 2:00 PM - 5:00 PM",
        "schedule_details": {
            "days": ["Sunday"],
            "start_time": "14:00",
            "end_time": "17:00"
        },
        "max_participants": 16,
        "participants": ["william@mergington.edu", "jacob@mergington.edu"]
    }
}

initial_teachers = [
    {
        "username": "mrodriguez",
        "display_name": "Ms. Rodriguez",
        "password": hash_password("art123"),
        "role": "teacher"
     },
    {
        "username": "mchen",
        "display_name": "Mr. Chen",
        "password": hash_password("chess456"),
        "role": "teacher"
    },
    {
        "username": "principal",
        "display_name": "Principal Martinez",
        "password": hash_password("admin789"),
        "role": "admin"
    }
]

diff --git a/src/static/app.js b/src/static/app.js
new file mode 100644
index 0000000..f18b940 100644
--- /dev/null
+++ a/src/static/app.js
@@ -1,0 +1,868 @@
document.addEventListener("DOMContentLoaded", () => {
  // DOM elements
  const activitiesList = document.getElementById("activities-list");
  const messageDiv = document.getElementById("message");
  const registrationModal = document.getElementById("registration-modal");
  const modalActivityName = document.getElementById("modal-activity-name");
  const signupForm = document.getElementById("signup-form");
  const activityInput = document.getElementById("activity");
  const closeRegistrationModal = document.querySelector(".close-modal");

  // Search and filter elements
  const searchInput = document.getElementById("activity-search");
  const searchButton = document.getElementById("search-button");
  const categoryFilters = document.querySelectorAll(".category-filter");
  const dayFilters = document.querySelectorAll(".day-filter");
  const timeFilters = document.querySelectorAll(".time-filter");

  // Authentication elements
  const loginButton = document.getElementById("login-button");
  const userInfo = document.getElementById("user-info");
  const displayName = document.getElementById("display-name");
  const logoutButton = document.getElementById("logout-button");
  const loginModal = document.getElementById("login-modal");
  const loginForm = document.getElementById("login-form");
  const closeLoginModal = document.querySelector(".close-login-modal");
  const loginMessage = document.getElementById("login-message");

  // Activity categories with corresponding colors
  const activityTypes = {
    sports: { label: "Sports", color: "#e8f5e9", textColor: "#2e7d32" },
    arts: { label: "Arts", color: "#f3e5f5", textColor: "#7b1fa2" },
    academic: { label: "Academic", color: "#e3f2fd", textColor: "#1565c0" },
    community: { label: "Community", color: "#fff3e0", textColor: "#e65100" },
    technology: { label: "Technology", color: "#e8eaf6", textColor: "#3949ab" },
  };

  // State for activities and filters
  let allActivities = {};
  let currentFilter = "all";
  let searchQuery = "";
  let currentDay = "";
  let currentTimeRange = "";

  // Authentication state
  let currentUser = null;

  // Time range mappings for the dropdown
  const timeRanges = {
    morning: { start: "06:00", end: "08:00" }, // Before school hours
    afternoon: { start: "15:00", end: "18:00" }, // After school hours
    weekend: { days: ["Saturday", "Sunday"] }, // Weekend days
  };

  // Initialize filters from active elements
  function initializeFilters() {
    // Initialize day filter
    const activeDayFilter = document.querySelector(".day-filter.active");
    if (activeDayFilter) {
      currentDay = activeDayFilter.dataset.day;
    }

    // Initialize time filter
    const activeTimeFilter = document.querySelector(".time-filter.active");
    if (activeTimeFilter) {
      currentTimeRange = activeTimeFilter.dataset.time;
    }
  }

  // Function to set day filter
  function setDayFilter(day) {
    currentDay = day;

    // Update active class
    dayFilters.forEach((btn) => {
      if (btn.dataset.day === day) {
        btn.classList.add("active");
      } else {
        btn.classList.remove("active");
      }
    });

    fetchActivities();
  }

  // Function to set time range filter
  function setTimeRangeFilter(timeRange) {
    currentTimeRange = timeRange;

    // Update active class
    timeFilters.forEach((btn) => {
      if (btn.dataset.time === timeRange) {
        btn.classList.add("active");
      } else {
        btn.classList.remove("active");
      }
    });

    fetchActivities();
  }

  // Check if user is already logged in (from localStorage)
  function checkAuthentication() {
    const savedUser = localStorage.getItem("currentUser");
    if (savedUser) {
      try {
        currentUser = JSON.parse(savedUser);
        updateAuthUI();
        // Verify the stored user with the server
        validateUserSession(currentUser.username);
      } catch (error) {
        console.error("Error parsing saved user", error);
        logout(); // Clear invalid data
      }
    }

    // Set authentication class on body
    updateAuthBodyClass();
  }

  // Validate user session with the server
  async function validateUserSession(username) {
    try {
      const response = await fetch(
        `/auth/check-session?username=${encodeURIComponent(username)}`
      );

      if (!response.ok) {
        // Session invalid, log out
        logout();
        return;
      }

      // Session is valid, update user data
      const userData = await response.json();
      currentUser = userData;
      localStorage.setItem("currentUser", JSON.stringify(userData));
      updateAuthUI();
    } catch (error) {
      console.error("Error validating session:", error);
    }
  }

  // Update UI based on authentication state
  function updateAuthUI() {
    if (currentUser) {
      loginButton.classList.add("hidden");
      userInfo.classList.remove("hidden");
      displayName.textContent = currentUser.display_name;
    } else {
      loginButton.classList.remove("hidden");
      userInfo.classList.add("hidden");
      displayName.textContent = "";
    }

    updateAuthBodyClass();
    // Refresh the activities to update the UI
    fetchActivities();
  }

  // Update body class for CSS targeting
  function updateAuthBodyClass() {
    if (currentUser) {
      document.body.classList.remove("not-authenticated");
    } else {
      document.body.classList.add("not-authenticated");
    }
  }

  // Login function
  async function login(username, password) {
    try {
      const response = await fetch(
        `/auth/login?username=${encodeURIComponent(
          username
        )}&password=${encodeURIComponent(password)}`,
        {
          method: "POST",
        }
      );

      const data = await response.json();

      if (!response.ok) {
        showLoginMessage(
          data.detail || "Invalid username or password",
          "error"
        );
        return false;
      }

      // Login successful
      currentUser = data;
      localStorage.setItem("currentUser", JSON.stringify(data));
      updateAuthUI();
      closeLoginModalHandler();
      showMessage(`Welcome, ${currentUser.display_name}!`, "success");
      return true;
    } catch (error) {
      console.error("Error during login:", error);
      showLoginMessage("Login failed. Please try again.", "error");
      return false;
    }
  }

  // Logout function
  function logout() {
    currentUser = null;
    localStorage.removeItem("currentUser");
    updateAuthUI();
    showMessage("You have been logged out.", "info");
  }

  // Show message in login modal
  function showLoginMessage(text, type) {
    loginMessage.textContent = text;
    loginMessage.className = `message ${type}`;
    loginMessage.classList.remove("hidden");
  }

  // Open login modal
  function openLoginModal() {
    loginModal.classList.remove("hidden");
    loginModal.classList.add("show");
    loginMessage.classList.add("hidden");
    loginForm.reset();
  }

  // Close login modal
  function closeLoginModalHandler() {
    loginModal.classList.remove("show");
    setTimeout(() => {
      loginModal.classList.add("hidden");
      loginForm.reset();
    }, 300);
  }

  // Event listeners for authentication
  loginButton.addEventListener("click", openLoginModal);
  logoutButton.addEventListener("click", logout);
  closeLoginModal.addEventListener("click", closeLoginModalHandler);

  // Close login modal when clicking outside
  window.addEventListener("click", (event) => {
    if (event.target === loginModal) {
      closeLoginModalHandler();
    }
  });

  // Handle login form submission
  loginForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    const username = document.getElementById("username").value;
    const password = document.getElementById("password").value;
    await login(username, password);
  });

  // Show loading skeletons
  function showLoadingSkeletons() {
    activitiesList.innerHTML = "";

    // Create more skeleton cards to fill the screen since they're smaller now
    for (let i = 0; i < 9; i++) {
      const skeletonCard = document.createElement("div");
      skeletonCard.className = "skeleton-card";
      skeletonCard.innerHTML = `
        <div class="skeleton-line skeleton-title"></div>
        <div class="skeleton-line"></div>
        <div class="skeleton-line skeleton-text short"></div>
        <div style="margin-top: 8px;">
          <div class="skeleton-line" style="height: 6px;"></div>
          <div class="skeleton-line skeleton-text short" style="height: 8px; margin-top: 3px;"></div>
        </div>
        <div style="margin-top: auto;">
          <div class="skeleton-line" style="height: 24px; margin-top: 8px;"></div>
        </div>
      `;
      activitiesList.appendChild(skeletonCard);
    }
  }

  // Format schedule for display - handles both old and new format
  function formatSchedule(details) {
    // If schedule_details is available, use the structured data
    if (details.schedule_details) {
      const days = details.schedule_details.days.join(", ");

      // Convert 24h time format to 12h AM/PM format for display
      const formatTime = (time24) => {
        const [hours, minutes] = time24.split(":").map((num) => parseInt(num));
        const period = hours >= 12 ? "PM" : "AM";
        const displayHours = hours % 12 || 12; // Convert 0 to 12 for 12 AM
        return `${displayHours}:${minutes
          .toString()
          .padStart(2, "0")} ${period}`;
      };

      const startTime = formatTime(details.schedule_details.start_time);
      const endTime = formatTime(details.schedule_details.end_time);

      return `${days}, ${startTime} - ${endTime}`;
    }

    // Fallback to the string format if schedule_details isn't available
    return details.schedule;
  }

  // Function to determine activity type (this would ideally come from backend)
  function getActivityType(activityName, description) {
    const name = activityName.toLowerCase();
    const desc = description.toLowerCase();

    if (
      name.includes("soccer") ||
      name.includes("basketball") ||
      name.includes("sport") ||
      name.includes("fitness") ||
      desc.includes("team") ||
      desc.includes("game") ||
      desc.includes("athletic")
    ) {
      return "sports";
    } else if (
      name.includes("art") ||
      name.includes("music") ||
      name.includes("theater") ||
      name.includes("drama") ||
      desc.includes("creative") ||
      desc.includes("paint")
    ) {
      return "arts";
    } else if (
      name.includes("science") ||
      name.includes("math") ||
      name.includes("academic") ||
      name.includes("study") ||
      name.includes("olympiad") ||
      desc.includes("learning") ||
      desc.includes("education") ||
      desc.includes("competition")
    ) {
      return "academic";
    } else if (
      name.includes("volunteer") ||
      name.includes("community") ||
      desc.includes("service") ||
      desc.includes("volunteer")
    ) {
      return "community";
    } else if (
      name.includes("computer") ||
      name.includes("coding") ||
      name.includes("tech") ||
      name.includes("robotics") ||
      desc.includes("programming") ||
      desc.includes("technology") ||
      desc.includes("digital") ||
      desc.includes("robot")
    ) {
      return "technology";
    }

    // Default to "academic" if no match
    return "academic";
  }

  // Function to fetch activities from API with optional day and time filters
  async function fetchActivities() {
    // Show loading skeletons first
    showLoadingSkeletons();

    try {
      // Build query string with filters if they exist
      let queryParams = [];

      // Handle day filter
      if (currentDay) {
        queryParams.push(`day=${encodeURIComponent(currentDay)}`);
      }

      // Handle time range filter
      if (currentTimeRange) {
        const range = timeRanges[currentTimeRange];

        // Handle weekend special case
        if (currentTimeRange === "weekend") {
          // Don't add time parameters for weekend filter
          // Weekend filtering will be handled on the client side
        } else if (range) {
          // Add time parameters for before/after school
          queryParams.push(`start_time=${encodeURIComponent(range.start)}`);
          queryParams.push(`end_time=${encodeURIComponent(range.end)}`);
        }
      }

      const queryString =
        queryParams.length > 0 ? `?${queryParams.join("&")}` : "";
      const response = await fetch(`/activities${queryString}`);
      const activities = await response.json();

      // Save the activities data
      allActivities = activities;

      // Apply search and filter, and handle weekend filter in client
      displayFilteredActivities();
    } catch (error) {
      activitiesList.innerHTML =
        "<p>Failed to load activities. Please try again later.</p>";
      console.error("Error fetching activities:", error);
    }
  }

  // Function to display filtered activities
  function displayFilteredActivities() {
    // Clear the activities list
    activitiesList.innerHTML = "";

    // Apply client-side filtering - this handles category filter and search, plus weekend filter
    let filteredActivities = {};

    Object.entries(allActivities).forEach(([name, details]) => {
      const activityType = getActivityType(name, details.description);

      // Apply category filter
      if (currentFilter !== "all" && activityType !== currentFilter) {
        return;
      }

      // Apply weekend filter if selected
      if (currentTimeRange === "weekend" && details.schedule_details) {
        const activityDays = details.schedule_details.days;
        const isWeekendActivity = activityDays.some((day) =>
          timeRanges.weekend.days.includes(day)
        );

        if (!isWeekendActivity) {
          return;
        }
      }

      // Apply search filter
      const searchableContent = [
        name.toLowerCase(),
        details.description.toLowerCase(),
        formatSchedule(details).toLowerCase(),
      ].join(" ");

      if (
        searchQuery &&
        !searchableContent.includes(searchQuery.toLowerCase())
      ) {
        return;
      }

      // Activity passed all filters, add to filtered list
      filteredActivities[name] = details;
    });

    // Check if there are any results
    if (Object.keys(filteredActivities).length === 0) {
      activitiesList.innerHTML = `
        <div class="no-results">
          <h4>No activities found</h4>
          <p>Try adjusting your search or filter criteria</p>
        </div>
      `;
      return;
    }

    // Display filtered activities
    Object.entries(filteredActivities).forEach(([name, details]) => {
      renderActivityCard(name, details);
    });
  }

  // Function to render a single activity card
  function renderActivityCard(name, details) {
    const activityCard = document.createElement("div");
    activityCard.className = "activity-card";

    // Calculate spots and capacity
    const totalSpots = details.max_participants;
    const takenSpots = details.participants.length;
    const spotsLeft = totalSpots - takenSpots;
    const capacityPercentage = (takenSpots / totalSpots) * 100;
    const isFull = spotsLeft <= 0;

    // Determine capacity status class
    let capacityStatusClass = "capacity-available";
    if (isFull) {
      capacityStatusClass = "capacity-full";
    } else if (capacityPercentage >= 75) {
      capacityStatusClass = "capacity-near-full";
    }

    // Determine activity type
    const activityType = getActivityType(name, details.description);
    const typeInfo = activityTypes[activityType];

    // Format the schedule using the new helper function
    const formattedSchedule = formatSchedule(details);

    // Create activity tag
    const tagHtml = `
      <span class="activity-tag" style="background-color: ${typeInfo.color}; color: ${typeInfo.textColor}">
        ${typeInfo.label}
      </span>
    `;

    // Create capacity indicator
    const capacityIndicator = `
      <div class="capacity-container ${capacityStatusClass}">
        <div class="capacity-bar-bg">
          <div class="capacity-bar-fill" style="width: ${capacityPercentage}%"></div>
        </div>
        <div class="capacity-text">
          <span>${takenSpots} enrolled</span>
          <span>${spotsLeft} spots left</span>
        </div>
      </div>
    `;

    activityCard.innerHTML = `
      ${tagHtml}
      <h4>${name}</h4>
      <p>${details.description}</p>
      <p class="tooltip">
        <strong>Schedule:</strong> ${formattedSchedule}
        <span class="tooltip-text">Regular meetings at this time throughout the semester</span>
      </p>
      ${capacityIndicator}
      <div class="participants-list">
        <h5>Current Participants:</h5>
        <ul>
          ${details.participants
            .map(
              (email) => `
            <li>
              ${email}
              ${
                currentUser
                  ? `
                <span class="delete-participant tooltip" data-activity="${name}" data-email="${email}">
                  
                  <span class="tooltip-text">Unregister this student</span>
                </span>
              `
                  : ""
              }
            </li>
          `
            )
            .join("")}
        </ul>
      </div>
      <div class="activity-card-actions">
        ${
          currentUser
            ? `
          <button class="register-button" data-activity="${name}" ${
                isFull ? "disabled" : ""
              }>
            ${isFull ? "Activity Full" : "Register Student"}
          </button>
        `
            : `
          <div class="auth-notice">
            Teachers can register students.
          </div>
        `
        }
      </div>
    `;

    // Add click handlers for delete buttons
    const deleteButtons = activityCard.querySelectorAll(".delete-participant");
    deleteButtons.forEach((button) => {
      button.addEventListener("click", handleUnregister);
    });

    // Add click handler for register button (only when authenticated)
    if (currentUser) {
      const registerButton = activityCard.querySelector(".register-button");
      if (!isFull) {
        registerButton.addEventListener("click", () => {
          openRegistrationModal(name);
        });
      }
    }

    activitiesList.appendChild(activityCard);
  }

  // Event listeners for search and filter
  searchInput.addEventListener("input", (event) => {
    searchQuery = event.target.value;
    displayFilteredActivities();
  });

  searchButton.addEventListener("click", (event) => {
    event.preventDefault();
    searchQuery = searchInput.value;
    displayFilteredActivities();
  });

  // Add event listeners to category filter buttons
  categoryFilters.forEach((button) => {
    button.addEventListener("click", () => {
      // Update active class
      categoryFilters.forEach((btn) => btn.classList.remove("active"));
      button.classList.add("active");

      // Update current filter and display filtered activities
      currentFilter = button.dataset.category;
      displayFilteredActivities();
    });
  });

  // Add event listeners to day filter buttons
  dayFilters.forEach((button) => {
    button.addEventListener("click", () => {
      // Update active class
      dayFilters.forEach((btn) => btn.classList.remove("active"));
      button.classList.add("active");

      // Update current day filter and fetch activities
      currentDay = button.dataset.day;
      fetchActivities();
    });
  });

  // Add event listeners for time filter buttons
  timeFilters.forEach((button) => {
    button.addEventListener("click", () => {
      // Update active class
      timeFilters.forEach((btn) => btn.classList.remove("active"));
      button.classList.add("active");

      // Update current time filter and fetch activities
      currentTimeRange = button.dataset.time;
      fetchActivities();
    });
  });

  // Open registration modal
  function openRegistrationModal(activityName) {
    modalActivityName.textContent = activityName;
    activityInput.value = activityName;
    registrationModal.classList.remove("hidden");
    // Add slight delay to trigger animation
    setTimeout(() => {
      registrationModal.classList.add("show");
    }, 10);
  }

  // Close registration modal
  function closeRegistrationModalHandler() {
    registrationModal.classList.remove("show");
    setTimeout(() => {
      registrationModal.classList.add("hidden");
      signupForm.reset();
    }, 300);
  }

  // Event listener for close button
  closeRegistrationModal.addEventListener(
    "click",
    closeRegistrationModalHandler
  );

  // Close modal when clicking outside of it
  window.addEventListener("click", (event) => {
    if (event.target === registrationModal) {
      closeRegistrationModalHandler();
    }
  });

  // Create and show confirmation dialog
  function showConfirmationDialog(message, confirmCallback) {
    // Create the confirmation dialog if it doesn't exist
    let confirmDialog = document.getElementById("confirm-dialog");
    if (!confirmDialog) {
      confirmDialog = document.createElement("div");
      confirmDialog.id = "confirm-dialog";
      confirmDialog.className = "modal hidden";
      confirmDialog.innerHTML = `
        <div class="modal-content">
          <h3>Confirm Action</h3>
          <p id="confirm-message"></p>
          <div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;">
            <button id="cancel-button" class="cancel-btn">Cancel</button>
            <button id="confirm-button" class="confirm-btn">Confirm</button>
          </div>
        </div>
      `;
      document.body.appendChild(confirmDialog);

      // Style the buttons
      const cancelBtn = confirmDialog.querySelector("#cancel-button");
      const confirmBtn = confirmDialog.querySelector("#confirm-button");

      cancelBtn.style.backgroundColor = "#f1f1f1";
      cancelBtn.style.color = "#333";

      confirmBtn.style.backgroundColor = "#dc3545";
      confirmBtn.style.color = "white";
    }

    // Set the message
    const confirmMessage = document.getElementById("confirm-message");
    confirmMessage.textContent = message;

    // Show the dialog
    confirmDialog.classList.remove("hidden");
    setTimeout(() => {
      confirmDialog.classList.add("show");
    }, 10);

    // Handle button clicks
    const cancelButton = document.getElementById("cancel-button");
    const confirmButton = document.getElementById("confirm-button");

    // Remove any existing event listeners
    const newCancelButton = cancelButton.cloneNode(true);
    const newConfirmButton = confirmButton.cloneNode(true);
    cancelButton.parentNode.replaceChild(newCancelButton, cancelButton);
    confirmButton.parentNode.replaceChild(newConfirmButton, confirmButton);

    // Add new event listeners
    newCancelButton.addEventListener("click", () => {
      confirmDialog.classList.remove("show");
      setTimeout(() => {
        confirmDialog.classList.add("hidden");
      }, 300);
    });

    newConfirmButton.addEventListener("click", () => {
      confirmCallback();
      confirmDialog.classList.remove("show");
      setTimeout(() => {
        confirmDialog.classList.add("hidden");
      }, 300);
    });

    // Close when clicking outside
    confirmDialog.addEventListener("click", (event) => {
      if (event.target === confirmDialog) {
        confirmDialog.classList.remove("show");
        setTimeout(() => {
          confirmDialog.classList.add("hidden");
        }, 300);
      }
    });
  }

  // Handle unregistration with confirmation
  async function handleUnregister(event) {
    // Check if user is authenticated
    if (!currentUser) {
      showMessage(
        "You must be logged in as a teacher to unregister students.",
        "error"
      );
      return;
    }

    const activity = event.target.dataset.activity;
    const email = event.target.dataset.email;

    // Show confirmation dialog
    showConfirmationDialog(
      `Are you sure you want to unregister ${email} from ${activity}?`,
      async () => {
        try {
          const response = await fetch(
            `/activities/${encodeURIComponent(
              activity
            )}/unregister?email=${encodeURIComponent(
              email
            )}&teacher_username=${encodeURIComponent(currentUser.username)}`,
            {
              method: "POST",
            }
          );

          const result = await response.json();

          if (response.ok) {
            showMessage(result.message, "success");
            // Refresh the activities list
            fetchActivities();
          } else {
            showMessage(result.detail || "An error occurred", "error");
          }
        } catch (error) {
          showMessage("Failed to unregister. Please try again.", "error");
          console.error("Error unregistering:", error);
        }
      }
    );
  }

  // Show message function
  function showMessage(text, type) {
    messageDiv.textContent = text;
    messageDiv.className = `message ${type}`;
    messageDiv.classList.remove("hidden");

    // Hide message after 5 seconds
    setTimeout(() => {
      messageDiv.classList.add("hidden");
    }, 5000);
  }

  // Handle form submission
  signupForm.addEventListener("submit", async (event) => {
    event.preventDefault();

    // Check if user is authenticated
    if (!currentUser) {
      showMessage(
        "You must be logged in as a teacher to register students.",
        "error"
      );
      return;
    }

    const email = document.getElementById("email").value;
    const activity = activityInput.value;

    try {
      const response = await fetch(
        `/activities/${encodeURIComponent(
          activity
        )}/signup?email=${encodeURIComponent(
          email
        )}&teacher_username=${encodeURIComponent(currentUser.username)}`,
        {
          method: "POST",
        }
      );

      const result = await response.json();

      if (response.ok) {
        showMessage(result.message, "success");
        closeRegistrationModalHandler();
        // Refresh the activities list after successful signup
        fetchActivities();
      } else {
        showMessage(result.detail || "An error occurred", "error");
      }
    } catch (error) {
      showMessage("Failed to sign up. Please try again.", "error");
      console.error("Error signing up:", error);
    }
  });

  // Expose filter functions to window for future UI control
  window.activityFilters = {
    setDayFilter,
    setTimeRangeFilter,
  };

  // Initialize app
  checkAuthentication();
  initializeFilters();
  fetchActivities();
});
diff --git a/src/static/index.html b/src/static/index.html
new file mode 100644
index 0000000..284d70b 100644
--- /dev/null
+++ a/src/static/index.html
@@ -1,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mergington High School Activities</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <header>
      <h1>Mergington High School</h1>
      <h2>Extracurricular Activities</h2>
      <div id="user-controls">
        <div id="user-status">
          <button id="login-button" class="icon-button">
            <span class="user-icon">👤</span>
            <span>Login</span>
          </button>
          <div id="user-info" class="hidden">
            <span id="display-name"></span>
            <button id="logout-button">Logout</button>
          </div>
        </div>
      </div>
    </header>

    <main>
      <section id="activities-container">
        <div class="main-content-layout">
          <!-- Left Sidebar for Filters -->
          <aside class="sidebar-filters">
            <h3>Filter Activities</h3>
            <!-- Search Box -->
            <div class="search-box">
              <input
                type="text"
                id="activity-search"
                placeholder="Search activities..."
              />
              <button id="search-button" aria-label="Search">
                <span class="search-icon">🔍</span>
              </button>
            </div>

            <div class="filter-container">
              <div class="filter-label">Filter by category:</div>
              <div class="category-filters" id="category-filters">
                <button class="category-filter active" data-category="all">
                  All
                </button>
                <button class="category-filter" data-category="sports">
                  Sports
                </button>
                <button class="category-filter" data-category="arts">
                  Arts
                </button>
                <button class="category-filter" data-category="academic">
                  Academic
                </button>
                <button class="category-filter" data-category="community">
                  Community
                </button>
                <button class="category-filter" data-category="technology">
                  Technology
                </button>
              </div>
            </div>

            <!-- Day Filter -->
            <div class="filter-container day-filter-container">
              <div class="filter-label">Filter by day:</div>
              <div class="day-filters" id="day-filters">
                <button class="day-filter active" data-day="">All Days</button>
                <button class="day-filter" data-day="Monday">Monday</button>
                <button class="day-filter" data-day="Tuesday">Tuesday</button>
                <button class="day-filter" data-day="Wednesday">
                  Wednesday
                </button>
                <button class="day-filter" data-day="Thursday">Thursday</button>
                <button class="day-filter" data-day="Friday">Friday</button>
                <button class="day-filter" data-day="Saturday">Saturday</button>
                <button class="day-filter" data-day="Sunday">Sunday</button>
              </div>
            </div>

            <!-- Time Filter -->
            <div class="filter-container time-filter-container">
              <div class="filter-label">Filter by time:</div>
              <div class="time-filters">
                <button class="time-filter active" data-time="">
                  All Times
                </button>
                <button class="time-filter" data-time="morning">
                  Before School
                </button>
                <button class="time-filter" data-time="afternoon">
                  After School
                </button>
                <button class="time-filter" data-time="weekend">Weekend</button>
              </div>
            </div>
          </aside>

          <!-- Activities Content -->
          <div class="activities-content">
            <div id="activities-list">
              <!-- Activities will be loaded here -->
              <p>Loading activities...</p>
            </div>
            <div id="message" class="hidden message"></div>
          </div>
        </div>
      </section>
    </main>

    <footer>
      <p>&copy; 2023 Mergington High School</p>
    </footer>

    <!-- Registration Modal -->
    <div id="registration-modal" class="modal hidden">
      <div class="modal-content">
        <span class="close-modal">&times;</span>
        <h3>Register for <span id="modal-activity-name"></span></h3>
        <form id="signup-form">
          <div class="form-group">
            <label for="email">Student Email:</label>
            <input
              type="email"
              id="email"
              required
              placeholder="your-email@mergington.edu"
            />
          </div>
          <input type="hidden" id="activity" value="" />
          <button type="submit">Register</button>
        </form>
      </div>
    </div>

    <!-- Login Modal -->
    <div id="login-modal" class="modal hidden">
      <div class="modal-content">
        <span class="close-login-modal">&times;</span>
        <h3>Teacher Login</h3>
        <form id="login-form">
          <div class="form-group">
            <label for="username">Username:</label>
            <input
              type="text"
              id="username"
              required
              placeholder="Enter your username"
            />
          </div>
          <div class="form-group">
            <label for="password">Password:</label>
            <input
              type="password"
              id="password"
              required
              placeholder="Enter your password"
            />
          </div>
          <button type="submit">Login</button>
        </form>
        <div id="login-message" class="hidden message"></div>
      </div>
    </div>

    <script src="app.js"></script>
  </body>
</html>
diff --git a/src/static/styles.css b/src/static/styles.css
new file mode 100644
index 0000000..83361b1 100644
--- /dev/null
+++ a/src/static/styles.css
@@ -1,0 +1,666 @@
/* Color palette */
:root {
  /* Primary colors */
  --primary: #1a237e;
  --primary-light: #534bae;
  --primary-dark: #000051;
  --primary-text: #ffffff;

  /* Secondary colors */
  --secondary: #ff6f00;
  --secondary-light: #ffa040;
  --secondary-dark: #c43e00;
  --secondary-text: #ffffff;

  /* Neutral colors */
  --background: #f5f5f5;
  --surface: #ffffff;
  --text-primary: #333333;
  --text-secondary: #666666;
  --border: #e0e0e0;
  --border-light: #f0f0f0;
  --border-focus: #d0d0d0;

  /* Feedback colors */
  --success: #2e7d32;
  --success-light: #e8f5e9;
  --warning: #ff9800;
  --warning-light: #fff3cd;
  --error: #c62828;
  --error-light: #ffebee;
  --info: #0c5460;
  --info-light: #d1ecf1;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: Arial, sans-serif;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.4;
  color: var(--text-primary);
  max-width: 1200px;
  margin: 0 auto;
  padding: 12px;
  background-color: var(--background);
  font-size: 0.9rem;
}

header {
  text-align: center;
  padding: 12px 0;
  margin-bottom: 15px;
  background-color: var(--primary);
  color: var(--primary-text);
  border-radius: 5px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

header h1 {
  margin-bottom: 5px;
  font-size: 1.6rem;
}

header h2 {
  font-size: 1.2rem;
}

main {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

section {
  background-color: var(--surface);
  padding: 15px;
  border-radius: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  width: 100%;
}

section h3 {
  margin-bottom: 15px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
  color: var(--primary);
  font-size: 1.1rem;
}

/* New Layout Styles */
.main-content-layout {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.sidebar-filters {
  padding: 15px;
  background-color: var(--surface);
  border-radius: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.sidebar-filters h3 {
  margin-bottom: 15px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
  color: var(--primary);
  font-size: 1.1rem;
}

.activities-content {
  flex: 1;
}

/* Desktop layout */
@media (min-width: 768px) {
  .main-content-layout {
    flex-direction: row;
    align-items: flex-start;
  }

  .sidebar-filters {
    width: 200px; /* Reduced from 250px to 200px to make the sidebar narrower */
    position: sticky;
    top: 15px;
    max-height: calc(100vh - 30px);
    overflow-y: auto;
  }

  .activities-content {
    flex: 1;
  }
}

#activities-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 15px;
  width: 100%;
}

.activity-card {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background-color: var(--surface);
  display: flex;
  flex-direction: column;
  height: 100%;
  transition: all 0.3s ease;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
  position: relative;
  overflow: hidden;
  font-size: 0.85rem;
}

.activity-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
  border-color: var(--border-focus);
}

.activity-card h4 {
  margin-bottom: 8px;
  color: var(--primary);
  font-size: 1rem;
  letter-spacing: 0.3px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-light);
}

.activity-card p {
  margin-bottom: 8px;
  line-height: 1.4;
}

.activity-card-actions {
  margin-top: auto;
  padding-top: 10px;
  display: flex;
  justify-content: center;
}

/* Activity Tag */
.activity-tag {
  position: absolute;
  top: 8px;
  right: 8px;
  background: #e8eaf6;
  color: #3949ab;
  font-size: 0.65rem;
  font-weight: bold;
  padding: 2px 6px;
  border-radius: 10px;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}

/* Capacity Indicator */
.capacity-container {
  margin: 8px 0;
  width: 100%;
}

.capacity-bar-bg {
  height: 6px;
  background-color: var(--border-light);
  border-radius: 3px;
  overflow: hidden;
}

.capacity-text {
  display: flex;
  justify-content: space-between;
  margin-top: 3px;
  font-size: 0.7rem;
  color: var(--text-secondary);
}

.capacity-full .capacity-bar-fill {
  background-color: var(--error);
}

.capacity-near-full .capacity-bar-fill {
  background-color: var(--warning);
}

.capacity-available .capacity-bar-fill {
  background-color: var(--success);
}

/* Participants list */
.participants-list {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border-light);
}

.participants-list h5 {
  color: var(--primary);
  margin-bottom: 5px;
  font-size: 0.8em;
}

.participants-list ul {
  list-style-type: none;
  padding-left: 0;
  margin: 0;
  max-height: 100px;
}

.participants-list li {
  padding: 2px 0;
  color: var(--text-secondary);
  font-size: 0.8em;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Buttons */
.register-button {
  background: linear-gradient(145deg, var(--secondary), var(--secondary-dark));
  color: var(--secondary-text);
  margin-top: 10px;
  padding: 6px 12px;
  width: 100%;
  font-weight: bold;
  border-radius: 20px;
  box-shadow: 0 2px 4px rgba(255, 111, 0, 0.2);
  transition: all 0.3s ease;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 0.85rem;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  border: none;
}

button {
  background-color: var(--primary);
  color: white;
  border: none;
  padding: 6px 12px;
  font-size: 0.85rem;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s;
}

/* Tooltip styles */
.tooltip {
  position: relative;
  display: inline-block;
  cursor: pointer;
}

.tooltip .tooltip-text {
  visibility: hidden;
  background-color: rgba(33, 33, 33, 0.9);
  color: #fff;
  text-align: center;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 0.8rem;
  position: absolute;
  z-index: 1;
  bottom: 125%;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity 0.2s, visibility 0.2s;
  width: max-content;
  max-width: 250px;
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

.tooltip:hover .tooltip-text {
  visibility: visible;
  opacity: 1;
}

/* Special positioning for delete participant tooltip */
.delete-participant {
  cursor: pointer;
}

.delete-participant .tooltip-text {
  left: auto;
  right: calc(100% + 8px);
  top: 50%;
  bottom: auto;
  transform: translateY(-50%);
  white-space: nowrap;
}

/* Activity skeletons for loading state */
.skeleton-card {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background-color: var(--surface);
  display: flex;
  flex-direction: column;
  height: 180px;
  position: relative;
  overflow: hidden;
}

.skeleton-line {
  height: 10px;
  margin-bottom: 8px;
  background: linear-gradient(
    90deg,
    var(--border-light) 25%,
    var(--border) 50%,
    var(--border-light) 75%
  );
  background-size: 200% 100%;
  border-radius: 3px;
  animation: shimmer 1.5s infinite;
}

.skeleton-title {
  height: 18px;
  width: 70%;
  margin-bottom: 10px;
}

.skeleton-text {
  width: 100%;
}

.skeleton-text.short {
  width: 60%;
}

@keyframes shimmer {
  0% {
    background-position: -200% 0;
  }
  100% {
    background-position: 200% 0;
  }
}

/* Modal animation */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.modal.show {
  opacity: 1;
}

.modal-content {
  background-color: var(--surface);
  padding: 18px;
  border-radius: 5px;
  width: 90%;
  max-width: 350px;
  position: relative;
  transform: translateY(-20px);
  transition: transform 0.3s ease;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
  border: 1px solid var(--border);
}

.modal.show .modal-content {
  transform: translateY(0);
}

.close-modal,
.close-login-modal {
  position: absolute;
  right: 12px;
  top: 8px;
  font-size: 18px;
  cursor: pointer;
  color: var(--text-secondary);
  line-height: 1;
}

/* Login modal specific styling */
#login-modal .modal-content h3 {
  color: var(--primary);
  font-size: 1.2rem;
  margin-bottom: 15px;
  text-align: center;
}

/* Hidden class - critical for showing/hiding elements */
.hidden {
  display: none !important;
}

/* More compact no-results message */
.no-results {
  text-align: center;
  padding: 12px;
  color: var(--text-secondary);
  background-color: var(--surface);
  border-radius: 6px;
  border: 1px dashed var(--border);
  margin: 10px 0;
  font-size: 0.85rem;
}

footer {
  text-align: center;
  margin-top: 20px;
  padding: 10px;
  color: var(--text-secondary);
  font-size: 0.8rem;
}

/* Search and Filter Components - Updated for Sidebar */
.search-box {
  display: flex;
  width: 100%;
  margin-bottom: 15px;
}

.search-box input {
  flex: 1;
  min-width: 0; /* Add this to prevent input from overflowing */
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-right: none;
  border-radius: 20px 0 0 20px;
  font-size: 0.85rem;
  transition: all 0.3s ease;
}

.search-box input:focus {
  outline: none;
  border-color: var(--primary-light);
  box-shadow: 0 0 0 2px rgba(83, 75, 174, 0.1);
}

.search-box button {
  width: 36px;
  height: 100%; /* Make button match input height */
  min-height: 32px; /* Ensure minimum clickable area */
  background: var(--primary);
  color: white;
  border: none;
  border-radius: 0 20px 20px 0;
  cursor: pointer;
  transition: background-color 0.3s ease;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.search-box button:hover {
  background-color: var(--primary-light);
}

.search-icon {
  font-size: 1rem;
}

.filter-container {
  margin-bottom: 15px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.filter-label {
  font-weight: 600;
  color: var(--text-primary);
  font-size: 0.8rem;
}

.category-filters,
.day-filters,
.time-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.category-filter,
.day-filter,
.time-filter {
  background-color: var(--background);
  border: 1px solid var(--border);
  color: var(--text-primary);
  padding: 4px 10px;
  border-radius: 15px;
  font-size: 0.75rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.category-filter.active,
.day-filter.active,
.time-filter.active {
  background-color: var(--primary);
  color: white;
  border-color: var(--primary-dark);
}

.category-filter:hover,
.day-filter:hover,
.time-filter:hover {
  background-color: var(--primary-light);
  color: white;
}

.time-filters {
  width: 100%;
}

.reset-button {
  background-color: var(--border);
  color: var(--text-primary);
  padding: 5px 12px;
  border-radius: 15px;
  font-size: 0.8rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  align-self: flex-start;
}

.reset-button:hover {
  background-color: var(--border-focus);
}

/* Responsive adjustments for mobile */
@media (max-width: 767px) {
  .sidebar-filters {
    margin-bottom: 15px;
  }

  .main-content-layout {
    flex-direction: column;
  }

  #activities-list {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  }

  .sidebar-filters {
    width: 100%;
  }
}

/* User controls in header */
#user-controls {
  position: absolute;
  top: 10px;
  right: 15px;
}

#user-status {
  display: flex;
  align-items: center;
}

#user-info {
  display: flex;
  align-items: center;
  gap: 10px;
}

#display-name {
  margin-right: 5px;
  font-weight: 500;
}

.icon-button {
  display: flex;
  align-items: center;
  gap: 5px;
  background-color: rgba(255, 255, 255, 0.2);
  border-radius: 20px;
  padding: 4px 12px;
  font-size: 0.85rem;
  transition: background-color 0.2s;
}

.icon-button:hover {
  background-color: rgba(255, 255, 255, 0.3);
}

.user-icon {
  font-size: 1rem;
}

#logout-button {
  padding: 3px 10px;
  background-color: rgba(255, 255, 255, 0.2);
  font-size: 0.8rem;
  border-radius: 20px;
}

#logout-button:hover {
  background-color: rgba(255, 255, 255, 0.3);
}
diff --git a/src/backend/routers/__init__.py b/src/backend/routers/__init__.py
new file mode 100644
index 0000000..f324c48 100644
--- /dev/null
+++ a/src/backend/routers/__init__.py
@@ -1,0 +1,2 @@
from . import activities
from . import auth
diff --git a/src/backend/routers/activities.py b/src/backend/routers/activities.py
new file mode 100644
index 0000000..143dcf9 100644
--- /dev/null
+++ a/src/backend/routers/activities.py
@@ -1,0 +1,127 @@
"""
Endpoints for the High School Management System API
"""

from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import RedirectResponse
from typing import Dict, Any, Optional, List

from ..database import activities_collection, teachers_collection

router = APIRouter(
    prefix="/activities",
    tags=["activities"]
)

@router.get("/", response_model=Dict[str, Any])
def get_activities(
    day: Optional[str] = None,
    start_time: Optional[str] = None,
    end_time: Optional[str] = None
) -> Dict[str, Any]:
    """
    Get all activities with their details, with optional filtering by day and time
    
    - day: Filter activities occurring on this day (e.g., 'Monday', 'Tuesday')
    - start_time: Filter activities starting at or after this time (24-hour format, e.g., '14:30')
    - end_time: Filter activities ending at or before this time (24-hour format, e.g., '17:00')
    """
    # Build the query based on provided filters
    query = {}
    
    if day:
        query["schedule_details.days"] = {"$in": [day]}
    
    if start_time:
        query["schedule_details.start_time"] = {"$gte": start_time}
    
    if end_time:
        query["schedule_details.end_time"] = {"$lte": end_time}
    
    # Query the database
    activities = {}
    for activity in activities_collection.find(query):
        name = activity.pop('_id')
        activities[name] = activity
    
    return activities

@router.get("/days", response_model=List[str])
def get_available_days() -> List[str]:
    """Get a list of all days that have activities scheduled"""
    # Aggregate to get unique days across all activities
    pipeline = [
        {"$unwind": "$schedule_details.days"},
        {"$group": {"_id": "$schedule_details.days"}},
        {"$sort": {"_id": 1}}  # Sort days alphabetically
    ]
    
    days = []
    for day_doc in activities_collection.aggregate(pipeline):
        days.append(day_doc["_id"])
    
    return days

@router.post("/{activity_name}/signup")
def signup_for_activity(activity_name: str, email: str, teacher_username: Optional[str] = Query(None)):
    """Sign up a student for an activity - requires teacher authentication"""
    # Check teacher authentication
    if not teacher_username:
        raise HTTPException(status_code=401, detail="Authentication required for this action")
    
    teacher = teachers_collection.find_one({"_id": teacher_username})
    if not teacher:
        raise HTTPException(status_code=401, detail="Invalid teacher credentials")
    
    # Get the activity
    activity = activities_collection.find_one({"_id": activity_name})
    if not activity:
        raise HTTPException(status_code=404, detail="Activity not found")

    # Validate student is not already signed up
    if email in activity["participants"]:
        raise HTTPException(
            status_code=400, detail="Already signed up for this activity")

    # Add student to participants
    result = activities_collection.update_one(
        {"_id": activity_name},
        {"$push": {"participants": email}}
    )

    if result.modified_count == 0:
        raise HTTPException(status_code=500, detail="Failed to update activity")
    
    return {"message": f"Signed up {email} for {activity_name}"}

@router.post("/{activity_name}/unregister")
def unregister_from_activity(activity_name: str, email: str, teacher_username: Optional[str] = Query(None)):
    """Remove a student from an activity - requires teacher authentication"""
    # Check teacher authentication
    if not teacher_username:
        raise HTTPException(status_code=401, detail="Authentication required for this action")
    
    teacher = teachers_collection.find_one({"_id": teacher_username})
    if not teacher:
        raise HTTPException(status_code=401, detail="Invalid teacher credentials")
    
    # Get the activity
    activity = activities_collection.find_one({"_id": activity_name})
    if not activity:
        raise HTTPException(status_code=404, detail="Activity not found")

    # Validate student is signed up
    if email not in activity["participants"]:
        raise HTTPException(
            status_code=400, detail="Not registered for this activity")

    # Remove student from participants
    result = activities_collection.update_one(
        {"_id": activity_name},
        {"$pull": {"participants": email}}
    )

    if result.modified_count == 0:
        raise HTTPException(status_code=500, detail="Failed to update activity")
    
    return {"message": f"Unregistered {email} from {activity_name}"}
diff --git a/src/backend/routers/auth.py b/src/backend/routers/auth.py
new file mode 100644
index 0000000..616917a 100644
--- /dev/null
+++ a/src/backend/routers/auth.py
@@ -1,0 +1,51 @@
"""
Authentication endpoints for the High School Management System API
"""

from fastapi import APIRouter, HTTPException
from typing import Dict, Any
import hashlib

from ..database import teachers_collection

router = APIRouter(
    prefix="/auth",
    tags=["auth"]
)

def hash_password(password):
    """Hash password using SHA-256"""
    return hashlib.sha256(password.encode()).hexdigest()

@router.post("/login")
def login(username: str, password: str) -> Dict[str, Any]:
    """Login a teacher account"""
    # Hash the provided password
    hashed_password = hash_password(password)
    
    # Find the teacher in the database
    teacher = teachers_collection.find_one({"_id": username})
    
    if not teacher or teacher["password"] != hashed_password:
        raise HTTPException(status_code=401, detail="Invalid username or password")
    
    # Return teacher information (excluding password)
    return {
        "username": teacher["username"],
        "display_name": teacher["display_name"],
        "role": teacher["role"]
    }

@router.get("/check-session")
def check_session(username: str) -> Dict[str, Any]:
    """Check if a session is valid by username"""
    teacher = teachers_collection.find_one({"_id": username})
    
    if not teacher:
        raise HTTPException(status_code=404, detail="Teacher not found")
    
    return {
        "username": teacher["username"],
        "display_name": teacher["display_name"],
        "role": teacher["role"]
    }