# Project export: SlugHub

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2025
- Tagline: UCSC Hub of useful tools and resources.
- Devpost: https://devpost.com/software/slughub
- GitHub: https://github.com/DNA-System-Corp/SlugHub
- Team: 2 GitHub contributor(s) — Mando (17 commits), Spencer Johnson (15 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# SlugHub

**Authors:** David Glover, Armando Barron, Spencer Johnson  

SlugHub is your all-in-one companion app for navigating life as a UCSC student. Designed with usability and practicality in mind, SlugHub features robust user authentication with secure password encryption. Users can create multiple accounts on a single device and seamlessly switch between them, with each user’s data securely isolated. During registration, SlugHub ensures that each username and email is unique. Passwords must be at least 8 characters long and are securely encrypted before being stored in our MongoDB database.

## Features

### 1. Class Schedule
Students can input their class schedules using a dropdown menu preloaded with UCSC’s standardized time blocks. For added flexibility, an “Other…” option allows for custom time entries. Each added class appears as a colored block on the schedule. Classes can be easily added or removed using live-updating buttons that sync directly with the database.

### 2. Student Resources Directory
This page provides quick access to commonly used UCSC student resources through a curated list of helpful hyperlinks.

### 3. Interactive Campus Map
Automatically routes you to your next class based on the current date and time. You can switch between different travel modes, including walking, biking, driving, and public transportation. Users can also scroll through their upcoming classes and return to the current one with intuitive navigation buttons.

### 4. UCSC Events Page
Powered by BeautifulSoup and requests, this page scrapes live event data happening around UCSC. Users can:
- **Pin events:** Keep events at the top of the list (pinned events also change color and can be unpinned with another click).
- **Hide events:** Remove events to allow space for new ones (each user sees a maximum of 15 events for optimal performance).
- **Add to 📆:** Add events to your schedule (displayed in a distinct color) and integrate with the interactive map features.

### 5. Class Forums
An anonymous, real-time forum system where students can join or create chatrooms for their classes. Posts appear instantly with no need for manual refresh, and all data is securely stored in the database. Forums are organized by department and course number, allowing for easy navigation and participation.

## Prize Tracks
- **Education**
- **Slug Hack**
- **MongoDB Sponsor**


## Detected evidence (automated analysis)

Indexed codebase: 5 recognized source files, 83 KB.
- HTML (language) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (7 of 7)

```
.gitignore
class_forum_scraper.py
eventscraper.py
main.py
map.html
README.md
requirements.txt
```

### Dependencies

- requirements.txt: bcrypt, beautifulsoup4, geocoder, pymongo, PyQt6, PyQt6-WebEngine, python-dotenv, requests

### Recent commits (newest first)

- added requirements.txt and updated readme with better formatting
- No code changes just removed an old file and changed the name of a file.
- fix
- password requirements added
- not gay
- d
- fixed max buttons
- Merge branch 'main' of https://github.com/DNA-System-Corp/SlugHub
- Label added to Map page
- other
- Map Page style fixes, Map route change fixed to display message if you try to go past the last class
- savior
- Full map implementation + cleanup of repo
- fixed fonts
- gradient added
- he
- forum
- more ui changes
- return key
- event

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
PyQt6
PyQt6-WebEngine
bcrypt
python-dotenv
requests
beautifulsoup4
pymongo
geocoder
```

### eventscraper.py

```python
# event_scraper.py
import requests
from bs4 import BeautifulSoup

BASE_URL = "https://calendar.ucsc.edu/calendar/"

def scrape_ucsc_events(start_page=1, max_pages=5):
    events = []

    for page_num in range(start_page, start_page + max_pages):
        url = f"{BASE_URL}{page_num}"
        response = requests.get(url)
        if response.status_code != 200:
            break

        soup = BeautifulSoup(response.text, "html.parser")
        event_cards = soup.find_all("div", class_="em-card")
        if not event_cards:
            break

        for card in event_cards:
            try:
                title = card.find("h3", class_="em-card_title").get_text(strip=True)
                date = card.find_all("p", class_="em-card_event-text")[0].get_text(strip=True)

                location = "—"
                price = "—"

                texts = card.find_all("p", class_="em-card_event-text")
                if len(texts) > 1:
                    location = texts[1].get_text(strip=True)

                price_tag = card.find("span", class_="em-price")
                if price_tag:
                    price = price_tag.get_text(strip=True)

                events.append({
                    "title": title,
                    "date": date,
                    "location": location,
                    "price": price
                })
            except Exception as e:
                print("Skipping a card due to error:", e)
                continue

    return events
```

### class_forum_scraper.py

```python
import requests
from bs4 import BeautifulSoup

BASE_URL = "https://catalog.ucsc.edu"
COURSES_URL = BASE_URL + "/en/Current/General-Catalog/Courses"

def fetch_all_ucsc_classes():
    """
    Returns a sorted list of all course codes found on the UCSC Catalog site.
    Example format: ["CSE 12", "CSE 107", "MATH 19A", "MATH 19B", ...]
    """
    all_classes = set()  # use a set to avoid duplicates

    # 1) Fetch the main "Courses" page
    resp = requests.get(COURSES_URL)
    if not resp.ok:
        print("Failed to fetch main courses page.")
        return []

    soup = BeautifulSoup(resp.text, "html.parser")
    
    # 2) Find all <a> that link to departmental sub-pages
    #    They typically look like: <a href="/en/Current/General-Catalog/Courses/CSE-Computer-Science-and-Engineering">
    department_links = []
    for link in soup.select("li.toccatalog a"):
        href = link.get("href", "")
        # skip any external or anchor link
        if href.startswith("/en/Current/General-Catalog/Courses/") and "http" not in href:
            department_links.append(BASE_URL + href)

    # 3) For each department link, open and parse course codes
    for dlink in department_links:
        d_resp = requests.get(dlink)
        if not d_resp.ok:
            continue

        d_soup = BeautifulSoup(d_resp.text, "html.parser")
        # Typically courses are in <h3> or <h2> elements with text like "CSE 107"
        # or they might appear in <li> / <span>. We'll do a guess:
        possible_courses = d_soup.select("h2.course-title, h3.course-title, li.course")
        
        for ctitle in possible_courses:
            text = ctitle.get_text(strip=True)
            # text might be "CSE 107 Computer Networking" or "CSE 107"
            # We just want "CSE 107" part
            # We'll assume the course code is always the first 1-2 tokens
            # But let's do a quick parse if possible
            parts = text.split()
            if len(parts) >= 2:
                dept = parts[0]
                course_num = parts[1]
                # Combine them
                # (You might refine logic to handle "19A", "19B", etc.)
                # If the first token isn't uppercase or doesn't look like a dept, skip
                if dept.isalpha() and not dept.endswith(":"):
                    code = dept + " " + course_num
                    all_classes.add(code)

    return sorted(all_classes)
```

### map.html

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Smart Map</title>
    <meta charset="utf-8" />
    <style>
      html, body, #map {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
  </head>
  <body>
    <div id="map"></div>

    <script>
      let map;
      let directionsService;
      let directionsRenderer;
      let userLocation = null;
      let mapReady = false;
      let webChannelReady = false;
      let currentTravelMode = "DRIVING";
      let pendingDestination = null;

      function notifyIfFullyReady() {
        if (mapReady && userLocation && webChannelReady && window.bridge?.mapReady) {
          console.log("🧠 All systems ready — notifying Python.");
          window.bridge.mapReady();
        }
      }

      window.initMap = function () {
        console.log("🗺️ initMap started");

        map = new google.maps.Map(document.getElementById("map"), {
          center: { lat: 36.9914, lng: -122.0609 },
          zoom: 14
        });

        directionsService = new google.maps.DirectionsService();
        directionsRenderer = new google.maps.DirectionsRenderer();
        directionsRenderer.setMap(map);

        // Setup WebChannel
        new QWebChannel(qt.webChannelTransport, function (channel) {
          window.bridge = channel.objects.bridge;
          webChannelReady = true;
          console.log("🔌 WebChannel ready");

          // ASYNC: get user location from Python
          window.bridge.getUserLocation().then((coords) => {
            if (coords && coords.lat && coords.lng) {
              userLocation = { lat: coords.lat, lng: coords.lng };
              console.log("📍 Location from Python:", userLocation);
              notifyIfFullyReady();

              if (pendingDestination) {
                const dest = pendingDestination;
                pendingDestination = null;
                console.log("🔁 Retrying deferred route to:", dest);
                createRoute(dest);
              }
            } else {
              console.warn("⚠️ getUserLocation() returned invalid data");
            }
          }).catch((err) => {
            console.error("❌ Failed to get location from Python:", err);
          });
        });

        mapReady = true;
        console.log("✅ Map object initialized");
      };

      function setTravelMode(mode) {
        if (["DRIVING", "WALKING", "BICYCLING", "TRANSIT"].includes(mode)) {
          currentTravelMode = mode;
          console.log("🚦 Travel mode set to:", mode);
        }
      }

      function createRoute(destination) {
        if (!mapReady || !userLocation) {
          console.warn("⚠️ Not ready, deferring route to:", destination);
          pendingDestination = destination;
          return;
        }

        console.log("📍 Routing from:", userLocation, "→", destination);

        const request = {
          origin: userLocation,
          destination: destination,
          travelMode: currentTravelMode
        };

        directionsService.route(request, (result, status) => {
          if (status === "OK") {
            directionsRenderer.setDirections(result);
            console.log("✅ Route displayed.");
          } else {
            console.error("❌ Route failed:", status);
          }
        });
      }
    </script>

    <script
      src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
      async defer>
    </script>
  </body>
</html>

```