Skip to content

fix: repair the timetable download and the calendar download's month - #140

Open
Pranjal-SB wants to merge 3 commits into
suduolabs:mainfrom
Pranjal-SB:fix/timetable-calendar-download
Open

fix: repair the timetable download and the calendar download's month#140
Pranjal-SB wants to merge 3 commits into
suduolabs:mainfrom
Pranjal-SB:fix/timetable-calendar-download

Conversation

@Pranjal-SB

Copy link
Copy Markdown
Contributor

Both download buttons on the academia page were broken. Fixing them turned up
two neighbouring problems with the same root, so they are here too, each in its
own commit.

Timetable download returned an error

/api/timetable read the timetable straight from the goscrape table, while
every other surface that shows the same data — the academia page and /view
reads it from the Go backend through fetchUserData().

That second source of truth needs NEXT_PUBLIC_SUPABASE_URL and
NEXT_PUBLIC_SERVICE_KEY. Without them createClient("", "") throws
supabaseUrl is required at module evaluation, so the route 500s — and
next build fails outright while collecting page data:

NEXT_PUBLIC_SUPABASE_URL is not defined
Error: supabaseUrl is required.
> Build error occurred
Error: Failed to collect page data for /api/ophours

The route now reads from fetchUserData() and returns the ImageResponse
directly instead of rebuffering it through a second Response.

Supabase still backs optional-hour saving, so the client stays — but it is
null when unconfigured rather than throwing at import, and /api/ophours
answers 503 in that case, which the editor already surfaces as a failed save.
A deployment without Supabase now builds and runs, losing only that one
feature.

Calendar download had the wrong month on it

Both the API route and the grid named the month with:

month.includes("Jul") ? months[index + 6] : months[index]

which only holds when the calendar starts in January or July.

The route tested the selected month's own string. Pick October in a
July-start calendar and "Oct '25" fails the includes("Jul") test, so it
falls through to months[3] — an image titled April over October's days.

The grid tested calendar[0] instead, so it survived July starts but not
others. A semester can begin in any month; CalendarHelper_test.go uses an
"Aug '26" fixture, and for that calendar the grid rendered January in
both the header and the download filename.

Months arrive as "Aug '26", so the name is now derived from that string in a
single shared helper and no longer depends on the array index at all.

Per-user API responses were marked publicly cacheable

next.config.ts and vercel.json both stamped
Cache-Control: public, max-age=300, stale-while-revalidate=600 on
/api/(.*). Headers configured this way override what a route handler sets, so
/api/timetable — a PNG of one student's timetable, built from their session
cookie — was advertised to shared caches as reusable, and /api/calendar lost
the CDN headers it sets for itself.

/api is now excluded from the blanket rule in both configs, and each route
declares its own caching.

Authenticated pages are still covered by the catch-all public, max-age=3600
rule. That is the same class of problem, but it wants a deliberate decision
about page caching rather than a drive-by change, so it is left alone here and
called out in the commit message.

Also

  • The timetable download link had no file extension; it is now
    Timetable-ClassPro.png.
  • /view and the calendar grid threw on missing timetable/calendar data
    instead of rendering empty.

Testing

$ bun frontend/utils/Times.test.ts
Times: ok

$ tsc --noEmit -p frontend/tsconfig.json
(clean)

$ cd frontend && bun run build
✓ Compiled successfully
Route (app) ... ƒ /api/calendar  ƒ /api/timetable  ƒ /api/ophours
BUILD_EXIT=0

utils/Times.test.ts is a plain node:assert script — no test runner is
configured in this repo, and it covers the month-naming case that regressed.

What is not covered: I have no live backend or Supabase project to point
this at, so neither download has been exercised end to end. The month-naming
bug is demonstrated by the index arithmetic above and the build failure is
reproduced verbatim, but clicking both buttons against a real deployment before
merging would be worth it.

Note for reviewers

frontend/utils/Database/index.ts is dead code — nothing in the repo imports
it. I guarded it rather than deleting it to keep this PR to the reported bugs;
it can go in a follow-up, or here if you would rather.

/api/timetable read the timetable straight from the goscrape table while
every other surface (the academia page, /view) reads it from the Go
backend. That second source of truth needed NEXT_PUBLIC_SUPABASE_URL and
NEXT_PUBLIC_SERVICE_KEY, and without them createClient("", "") throws
"supabaseUrl is required" at module evaluation: the route 500s and
`next build` fails outright while collecting page data.

Read from fetchUserData() instead, which is where the rest of the app
already gets the same data, and return the ImageResponse directly rather
than rebuffering it through a second Response.

Supabase still backs optional-hour saving, so keep the client but let it
be null when unconfigured instead of throwing at import. /api/ophours
answers 503 in that case, which the editor already surfaces as a failed
save.

Also give the download link a filename with an extension, and guard the
timetable lookups so a missing timetable renders empty instead of
throwing.
The downloaded image was titled with the wrong month. Both the API route
and the grid picked a name with

  month.includes("Jul") ? months[index + 6] : months[index]

which only holds when the calendar starts in January or July. The route
tested the selected month's own string, so October in a July-start
calendar fell to the else branch and produced an image titled "April"
over October's days. The grid tested calendar[0] instead, so it survived
July starts but not others - a semester can start in any month, as
CalendarHelper_test.go's "Aug '26" fixture shows, and there it rendered
January.

Months arrive as "Aug '26", so derive the name from that string in one
shared helper. The result no longer depends on the array index at all.
The same helper feeds the download filename.
next.config.ts and vercel.json both stamped

  Cache-Control: public, max-age=300, stale-while-revalidate=600

on /api/(.*). Headers configured this way override what a route handler
sets, so /api/timetable - a PNG of one student's timetable, built from
their session cookie - was advertised to shared caches as reusable, and
/api/calendar lost the CDN headers it sets for itself.

Exclude /api from the blanket rule in both configs and let each route
declare its own caching.

Note that authenticated pages are still covered by the catch-all
public, max-age=3600 rule. That is the same class of problem and wants a
deliberate decision about page caching, so it is left alone here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant