Skip to content

Repository files navigation

Internship Tracker — Summer 2027

A personal full-stack web app that auto-scrapes SWE, Quant, and CS Research internship listings for Summer 2027 and tracks application status through a Kanban dashboard.


How It Works

Scraper (Railway, daily 8 AM CT)
  └── Fetches GitHub internship READMEs + Simplify.jobs
  └── Deduplicates, classifies role type, writes to Supabase

FastAPI (Vercel serverless)
  └── Serves listings + applications from Supabase

React frontend (Vercel)
  └── Browse listings → Save → Track on Kanban board

Features

  • Auto-scraping — pulls Summer 2027 internships daily from GitHub listing repos and Simplify.jobs, with dedup + role classification (SWE / Quant / CS Research).
  • Listings browser — filter by role, source, company, or search; recently-scraped roles get a "New" badge so the freshest postings are easy to spot and save.
  • Kanban tracker — drag applications across the status flow (saved → applied → … → offer / rejected), plus a flat newest-first list view. Cards show the date added and flag recent additions.
  • Dashboard — application counts by status, a response-rate stat, a Recharts bar chart, and upcoming deadlines (next 14 days).
  • Optional API auth — a shared-secret X-API-Key gate that can be toggled on with a single env var.

Stack

Layer Tech
Frontend React 18 + Vite + TailwindCSS + @dnd-kit
API FastAPI (Python 3.12) + Pydantic
Database Supabase (PostgreSQL)
Scraper Playwright + BeautifulSoup4 + httpx + APScheduler
Frontend hosting Vercel (Hobby)
API hosting Vercel serverless functions
Scraper hosting Railway (worker process)

Project Structure

internship-tracker/
├── frontend/             React + Vite + TailwindCSS
│   └── src/
│       ├── api/client.js         fetch wrapper — all API calls
│       ├── utils/recency.js      shared "New" badge / date helpers
│       ├── pages/
│       │   ├── Listings.jsx      table + filter bar + "Save to Tracker" (+ "New" badges)
│       │   ├── Tracker.jsx       Kanban board (drag-and-drop) + flat newest-first list view
│       │   └── Dashboard.jsx     stat cards, bar chart, deadlines
│       └── components/
│           ├── FilterBar.jsx
│           ├── KanbanCard.jsx    card with "Added" date + "New" badge
│           ├── StatsPanel.jsx
│           └── DeadlineBanner.jsx
├── api/                  FastAPI (Vercel serverless)
│   ├── index.py          app entrypoint + CORS
│   ├── auth.py           optional shared-secret API key check
│   ├── routers/          listings.py, applications.py, stats.py
│   ├── models/           listing.py, application.py
│   └── db.py             Supabase client (anon key, read-only)
├── scraper/              Python scraper + APScheduler (Railway)
│   ├── main.py           scheduler entrypoint — daily 8:00 AM CT
│   ├── sources/          github_jobs.py, simplify.py
│   ├── pipeline/         dedup.py, filter.py, writer.py
│   └── seed.py           seed Supabase with sample listings for dev
├── supabase/schema.sql   DB schema — run once in Supabase SQL editor
├── vercel.json           build config + /api/* rewrites
├── .vercelignore         hides Railway-only deps from the Vercel build
├── Procfile              Railway worker start command
├── requirements.txt      root deps for Railway (Railpack) — not used by Vercel
├── .python-version       pins Python 3.12 for Railway
└── .env.example          required env vars template

Application Status Flow

saved → applied → oa_received → oa_submitted →
interview_scheduled → interview_done → offer → rejected / withdrawn

Role Types (Zones)

Zone Matched On
swe software, engineer, developer, backend, frontend, infrastructure…
quant quantitative, trading, algorithmic, derivatives…
cs_research research, ML, AI, NLP, computer vision, deep learning…
other everything else that passes the include filter

Scraper Sources

Source Method Status
GitHub (vanshb03 / SimplifyJobs) httpx — parses README HTML tables + markdown Active
Simplify.jobs Playwright — JS-rendered SPA scrape Active (CF may block)

The scraper runs daily at 8:00 AM CT via APScheduler on Railway. Each source is isolated in a try/except — one failure never stops the others.


Local Development

Prerequisites

  • Python 3.12+
  • Node 18+
  • A Supabase project with supabase/schema.sql applied

Setup

# 1. Copy env template and fill in your values
cp .env.example .env

# 2. API (from api/)
pip install -r api/requirements.txt
uvicorn index:app --reload --port 8000

# 3. Frontend (from frontend/)
npm install
npm run dev        # localhost:5173 — proxies /api/* to :8000

# 4. Scraper — one immediate run for testing (from repo root)
pip install -r requirements.txt
playwright install chromium
python -m scraper.main --run-now

# 5. Seed sample data (optional)
python -m scraper.seed

Environment Variables

Variable Used By Description
SUPABASE_URL API + Scraper Your Supabase project URL
SUPABASE_ANON_KEY API Read-only key for FastAPI
SUPABASE_SERVICE_ROLE_KEY Scraper Write key — never expose to frontend
SCRAPER_CRON_HOUR Scraper Hour to run daily scrape (default: 8)
SCRAPER_CRON_MINUTE Scraper Minute offset (default: 0)
SCRAPER_TIMEZONE Scraper Timezone string (default: America/Chicago)
API_KEY API Optional. When set, /api/* data routes require header X-API-Key. Unset = open API.
VITE_API_KEY Frontend Optional. Same value as API_KEY; baked into the build so the frontend sends the header.

See .env.example for the full template.

API auth is optional and off by default. Set API_KEY (API) and VITE_API_KEY (frontend) to the same value and redeploy to lock the API down; leave both unset to keep it open. /api/health is always open.


Deployment

Service Hosts Notes
Vercel Hobby React frontend + FastAPI Auto-deploys on push to main. 10s function timeout. Builds the API from api/requirements.txt.
Railway Python scraper + APScheduler Persistent worker process. Set Build Command to playwright install chromium.
Supabase PostgreSQL Run supabase/schema.sql once to create tables.

Dependency layout: the root requirements.txt holds the scraper's heavy deps (Playwright, lxml) for Railway only. .vercelignore hides it from Vercel so the API builds from the lightweight api/requirements.txt — keep these separate to avoid Vercel trying to compile lxml.


Database Schema

Defined in supabase/schema.sql. Two tables:

  • listings — scraped internship postings (id, title, company, location, role_type, source, url, deadline, is_active, posted_at, created_at)
  • applications — tracked applications (id, listing_id, company, role, status, applied_at, deadline, notes, oa_date, interview_date, offer_deadline, created_at, updated_at)

License

MIT © Devon Lopez

Releases

Packages

Contributors

Languages