Jake Fung

UBSeats

Map-first study room finder for UBC Vancouver

Visit
  • TypeScript
  • React
  • Supabase
  • Mapbox

UBSeats is a map-first study spot finder for UBC Vancouver. Browse an interactive campus map, check live hours, and see room details before you head out the door, currently there are over 500 rooms across 50+ buildings.

The problem UBSeats is solving

UBC has hundreds of places to study, spread across libraries, classrooms and lounges in dozens of buildings. That information is scattered across library booking pages, course timetables and building hours, so there is no single place to look. I also noticed that students don't really know they can use the classrooms when they are unoccuppied. During exams, students end up walking from library to library hoping to find a free seat, while some classrooms sit empty.

I ran into this myself last March. Every spot I knew was packed, and no app could tell me where else to go, so I decided to build one.

The first version

The first version of UBSeats, a list with filters

The first version was a list of buildings with filters. It was easy to build, but it never really worked. Students don't ask "which buildings are libraries or classrooms?" They ask "which rooms are available right now in the building I'm in?" Solving that meant showing availability and location together, which is why UBSeats became map-first.

What UBSeats Does

The Hard Parts

15-minutes pipeline on fetching library bookings

Library study rooms are booked through LibCal across two hosts: the UBC Library and the AMS for Nest.

To fetch the bookings, I had to reverse-engineer the LibCal API by inspecting the network requests made by the LibCal booking page.

One trap was that the endpoint requires a location id and a group id along with the room id, and neither one can be derived from the room. Leaving one out will result in a 400 error. Passing the wrong ones will return an empty result. So the sync first loads each room's page, pulls both ids out of the config embedded in the HTML, and caches them.

Time zones were the other trap. LibCal returns timestamps with no offset, and it was running the sync in UTC. This brings a problem when it's 5pm Vancouver time, UTC is already on tomorrow's date, so an evening sync would fetch the wrong day. The solution was to work out the Vancouver calendar date and the PT offset (luckily BC transitioned to a consistent offset all year), so the times stay correct.

A Supabase Edge Function does all of this every 15 minutes. It goes through the rooms four at a time with a short pause between batches, so it doesn't hammer LibCal, and stores each room's slots in Postgres.

The LibCal pipeline
The LibCal pipeline

Web-scraping for classroom availability

Classrooms were the harder source. Their schedule lives in UBC's timetabling system, Scientia, behind a CWL login with Duo. There is no API and no feed. The only way in is having to manually fill out the form.

Scientia only says when a room is booked, so UBSeats turns that around: any gap between bookings from 7am to 10pm counts as free. However, there is a risk that a room that got dropped by mistake shows up as free all day. So I made the scraper strict:

  • Unparseable locations stop the run. If any room name doesn't match the <BUILDING CODE> <ROOM NUMBER> format, it exits and writes nothing.
  • Writes are idempotent. A single database call deletes the bookings in the scraped window and inserts the fresh set, so running it again is always safe.
  • Stale data disappears. If the latest scrape no longer covers today, the app shows no availability at all instead of a fully free day.

Because of the CWL gate, the scraper can't run in the cloud. I run it myself from campus or over the VPN, with the login session saved so I only go through Duo when it expires. There are 3,500+ bookings across 328 rooms per run. It's the least automated part of UBSeats, and also the one I trust the most.

The Scientia scraper
The Scientia scraper

Full Architecture

The UBSeats architecture diagram
Architecture diagram
LayerChoice
FrontendReact, Tailwind, shadCN/ui
Query handlingTanStack Query
MapMapbox GL
DataSupabase (Postgres)
HostingVercel
← back to work