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.
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
- 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-Keygate that can be toggled on with a single env var.
| 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) |
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
saved → applied → oa_received → oa_submitted →
interview_scheduled → interview_done → offer → rejected / withdrawn
| 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 |
| 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.
- Python 3.12+
- Node 18+
- A Supabase project with
supabase/schema.sqlapplied
# 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| 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) andVITE_API_KEY(frontend) to the same value and redeploy to lock the API down; leave both unset to keep it open./api/healthis always open.
| 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.txtholds the scraper's heavy deps (Playwright, lxml) for Railway only..vercelignorehides it from Vercel so the API builds from the lightweightapi/requirements.txt— keep these separate to avoid Vercel trying to compilelxml.
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)
MIT © Devon Lopez