DEV Community

ashmitamajumdar30
ashmitamajumdar30

Posted on

We built a hackathon judging platform because we were tired of spreadsheets

If you've ever helped organize a hackathon, you know the drill: a Google Form for submissions, a separate spreadsheet for judges to fill in scores, someone manually averaging everything in Excel at 2am before results need to go out. We wanted to fix that, so we built Podium — Next.js, Postgres, Drizzle, Docker, the whole thing self-hostable so you're not locked into someone else's SaaS.

The basics are what you'd expect: teams form with a join code, they submit a project before a deadline that locks automatically, judges get invited and score against a rubric, there's a leaderboard at the end. Nothing revolutionary. But a few things along the way taught us more than we expected.

The bug that actually mattered-

Early on, judge assignment was dead simple — invite a judge, assign them to every submission. Worked great in testing. Then we tested it properly, with an account that was both a participant and a judge (which, let's be honest, happens constantly in small hackathons — half your judges probably also want to compete). And there it was: that judge showed up on the leaderboard scoring their own team's project. Nobody wrote that bug on purpose, it's just what happens when "assign everyone to everything" meets "people wear two hats."

Fixing it meant checking, before any assignment happens, whether the judge is actually a member of that submission's team — and skipping it if so. Simple once you see it. The annoying part was we had to apply that same check in two different places: once when a judge gets invited, and again whenever a new submission comes in afterward and we re-sync assignments. Miss either one and the bug's back.

The access control scare-

At some point we noticed the admin dashboard was loading fine for someone who wasn't even signed in. Spent a good chunk of time assuming the auth library was broken, double checking cookies, suspecting caching — turned out the actual page just... never checked who was looking at it. It queried the database and rendered, full stop. No guard, nothing. Once we added an explicit check at the top of the page (and told Next.js not to statically cache that route), it was fixed instantly.

The lesson that stuck with us: in a Next.js app with server components, nothing protects a page by default. You have to explicitly ask "is this person allowed to be here" on every single page that needs it. Forget one, and it's wide open.

Where it's at now-

Teams, submissions, rubrics, judge invites, scoring, a weighted leaderboard, CSV export — it all works end to end, we tested it ourselves start to finish with real (if silly) test data. What's left is mostly polish: the scoring UI could look nicer, and right now every judge scores every submission, which is fine for a small event but would get unwieldy for something with hundreds of entries. Round-robin assignment is the obvious next step if we keep building this out.

Top comments (2)

Collapse
 
arhancanli profile image
Arhan Canli •

The judge-who-is-also-a-participant bug is a great catch, and "nothing protects a page by default" is the lesson most Next.js apps learn the hard way.

One thing worth deciding before real events use the weighted leaderboard: judges don't use the same scale. One judge's 7 is another's 9, and when each judge only scores some submissions, a plain average rewards the teams that happened to get the generous judges. Two cheap guards:

  • Normalise per judge before averaging (convert each judge's scores to how far each one is above or below that judge's own average, in units of their spread), then rescale for display. A judge who gives everything 8 to 9 and one who uses 3 to 10 then count equally.
  • Be careful with judges who scored only one or two submissions. With two scores, the normalised values are always exactly +1 and -1, no matter how close the raw scores were, so those judges push hardest on the ranking. Shrinking each judge's mean and spread toward the overall averages fixes that, or at minimum flagging judges below some number of scores.

Someone else here tested exactly this on a real hackathon's data and found that 13 of 41 projects moved three or more places once judges were normalised, so it's not a cosmetic choice.

Collapse
 
elijahbrown profile image
Elijah Brown •

The dual-hat judge-who-is-also-a-participant bug is the useful catch. When you invent those silly test teams and judge invites, use reserved fiction phones (US 555-0100 to 555-0199, UK 020 7946 0xxx) and domains you own, so a leftover CSV export during the walkthrough cannot publish a real contact.