Client project

CUNYServecivic-engagement platform

A platform that helps City University of New York students find volunteering, pre-health hospital placements and community events in one place.

Client
City University of New York
Built
August 2025, 19 commits over 11 days
Stack
Node.js, Express, MongoDB, Puppeteer
The problem

Opportunities spread across three websites

CUNY events, admissions information sessions and New York City volunteering opportunities each live on their own site. Students had to check all three, and nothing tracked who had signed up or finished orientation.

What I built

One calendar, kept fresh automatically

CUNYServe architecture: scrapers feed MongoDB, an Express API serves student and admin pagesevents.cuny.eduCUNY eventsCUNY admissionsinfo sessionsNYC Servicecity volunteeringPuppeteer scrapersnightly cron, 02:00MongoDBupsert by source URLExpress API26 routesAuthGoogle sign-in, JWTStudent pagesevents, onboardingAdmin dashboardscrape logs, users
How the pieces connect. Event data is scraped from three public sources every night, so students see one up-to-date calendar instead of checking three sites.
  • Scrapers. Three Puppeteer scrapers run every night at 02:00. Each event is upserted by its source URL, so re-running a scrape updates events instead of duplicating them. Every run writes a log with new, updated and failed counts.
  • Review before publishing. Scraped events land in an admin queue. An administrator reviews them and sees the scrape history before anything reaches students.
  • Accounts. Google sign-in or email and password (bcrypt, JWT in cookies), a two-step sign-up, profiles, and email password reset through Nodemailer.
  • Onboarding. A volunteer orientation flow that records which students have completed it.
  • Student pages. Volunteer projects, a pre-health hospital volunteering track, an events calendar (FullCalendar), FAQ and code of conduct.
  • Deployment. Express API and static pages on Vercel, with admin-only routes behind separate middleware.
What I would change

Lessons

Scrapers break when a source site changes its layout. The logs made failures visible, but the next step would be an alert when a scraper returns zero events several nights running, and tests that run each scraper against a saved copy of the page.