Raptor runs the whole hackathon.
An open, self-hostable submission & judging platform โ built backend-first,
so role isolation and deadlines are never optional. docker compose up
and there is no cloud account to sign up for.
Hackathon tooling makes organizers
choose between trust and control.
Every mainstream option asks an organizer to hand over event data, judge scores, and participant trust to somebody else's servers โ or to build permission checks that only hold up as long as nobody opens dev tools.
Vendor lock-in
Cloud-only SaaS tools own the event's data, its judges' scores, and its certificates โ with no self-host path out.
Client-side trust
A permission check that "works" only because a button is hidden or disabled is not a permission check.
Deadline theater
A countdown timer is not a deadline if the server never actually checks it at the moment of the write.
One platform. The entire lifecycle.
Enforced server-side, every time.
Registration, team formation, submissions, automated verification, judge assignment, weighted + bonus scoring, cross-judge normalization, dense-rank results with genuine tie-sharing, public voting, signed certificates, comments, and a platform-wide leaderboard โ on infrastructure an organizer actually controls.
Self-hosted, top to bottom.
Plain protocols and self-hosted primitives only โ Postgres, Redis, and this code. No cloud database, no auth-as-a-service, no hosted CAPTCHA, no provider-specific mail API.
api · NestJS
The only service web ever talks to. Every scoped guard lives here, server-side, never trusted from the client.
web · Next.js
Purely a client of api over HTTP โ no network path to Postgres or Redis exists at all.
worker · BullMQ
A separate process for anything async/slow: submission verification checks and global-ranking recomputes.
postgres + ๐ฅ redis
Internal-network-only, zero published host ports, ever, under any configuration.
๐ Auth & Role Isolation
There is no global "is this user an organizer" check anywhere in this codebase โ every privileged route is scoped to one event, resolved from the request path.
Argon2 + hashed tokens
Passwords hashed with argon2. Session, verification, and invitation tokens are random, high-entropy, and stored hashed โ the raw value never persists after issuance.
Per-event RBAC
@RequireEventRole(...) resolves eventId from the path and checks membership for that exact event, not a global role.
Verified by design
Email verification, forced password set on a staff account's first login โ no dormant unverified accounts with standing access.
No UI-only gates
A check that "works" only because a button is hidden client-side does not count โ guards are tested by calling the route directly.
๐ Event Lifecycle & Deadlines
12 real phases, not a single status flag โ and every deadline is checked against server time, at the moment of the write.
Never a client timestamp
A disabled button, a countdown timer, or a forged submittedAt field is never trusted as a substitute for a real server check.
Re-checked on every write
Not just on page load โ every single mutating request re-validates the current phase before it's allowed to happen.
๐ฅ Team Management
Form a team in seconds, with a roster that locks the instant it matters.
Shareable invite link
Create or join a team with one link โ no email round-trip required to get a squad together.
Roster management
Kick a member, regenerate the invite link โ full control while the team is still forming.
Permanent lock on submit
The roster locks the moment the team submits โ keyed directly off the submission record, not a separate timer.
๐ค Submissions & Public Gallery
Public-by-default โ the gallery is viewable with no login at all.
Draft → submit → resubmit
Unlimited resubmission right up until the deadline โ no penalty for iterating.
Searchable, filterable
The public gallery supports real text search and track filtering โ not just a static list.
Content-verified uploads
Files are validated by actual magic bytes, never extension or client MIME type, and re-encoded server-side before storage.
โ Automated Verification
Every submission is checked against its real GitHub repo before a judge ever sees it.
Real GitHub repo check
An async check runs against the submitted repository on a separate BullMQ worker process โ never inline with the request, never blocking the submit.
Organizer approval gate
An organizer explicitly approves or disqualifies a submission before it reaches judging โ verification informs a human, it never auto-decides.
Encrypted access token
An admin-supplied GitHub token is AES-256-GCM encrypted at rest โ never stored as raw, usable plaintext.
Documented timestamp source
Verification reads one specific, documented timestamp (commit or push time) against the event window โ not a forgeable client value.
โ๏ธ Judge Assignment
Manual or algorithmic โ and once a judge scores a project, that assignment is permanent.
Manual or algorithmic
Assign by hand for a small event, or let the algorithm balance load across judges automatically.
Per-judge caps
A configurable cap per judge, with an explicit organizer override when real life needs an exception.
No-show reassignment
A judge who never shows can be reassigned cleanly โ but only before they've actually submitted a score.
๐ Scoring & Rubric
Organizer-defined, weighted, and never silently overwritten.
Weighted rubric
Organizers define the exact criteria and weights a judge scores against โ no one-size-fits-all rubric.
Bonus tracks & awards
Optional bonus-track scoring plus special-award nominations, layered on top of the core rubric.
Immutable revision history
A judge can revise a score any number of times โ every prior version is kept, never overwritten.
๐ Cross-Judge Normalization
One harsh judge, or one generous one, can't quietly decide a whole event's outcome.
Per-judge z-score calibration
Every judge's scores are calibrated against their own scoring history, with a documented fallback for judges with too little data.
Locks when results go live
The normalization method is permanently locked the moment results are published โ no retroactive method-shopping.
๐ Results & Dense-Rank Leaderboard
Genuine ties share a place. Never an arbitrary coin-flip tiebreak.
Dense ranking
Two teams with the identical score both show rank 1 โ the next real rank is 2, not 3.
Draft → publish
Results are staged as a draft, reviewed, then explicitly published โ never live by accident.
Audited corrections
Any post-publish correction is fully logged โ what changed, by whom, and why.
๐ณ๏ธ Public Voting
A real audience ballot โ without handing anti-abuse to a third party.
Single-choice ballot
A curated shortlist, one vote per verified voter, per round.
Self-hosted proof-of-work
Anti-Sybil / anti-ballot-stuffing CAPTCHA, computed and checked entirely in-house โ zero third-party CAPTCHA dependency, by design.
Tallies hidden mid-round
Results stay hidden until the round actually closes, so an early leader can't sway the votes still coming in.
๐ Signed Certificates
A certificate that can actually prove it's real.
Ed25519 signed
Every participation and winner certificate is cryptographically signed โ not just a PDF that claims to be official.
Independently verifiable
Anyone can verify a certificate's signature โ the download and the live web view run the exact same verification code path.
Public certificate gallery
Earned certificates are browsable publicly โ a real, checkable record, not a private download link.
๐ฌ Comments & ๐ Global Ranking
Discussion that's safe to render, and a reputation that follows you across events.
Sanitized, threaded comments
One shared sanitization code path, used identically everywhere content renders โ preview, production, certificate view, download โ never two implementations that could silently drift.
Platform-wide leaderboard
A single ranking across every event a person has ever competed in, with its own tie-break rules and a per-person history drill-down.
๐ฅ๏ธ Organizer Shell & ๐ญ Demo Mode
One dashboard to run the whole event โ and one flag to try the whole lifecycle risk-free.
Unified admin shell
Event setup, verification queue, assignment board, scoring oversight, results publishing, CSV export, and a per-event audit-log viewer, all in one place.
DEMO_MODE=true
Spins up a fully isolated sandbox database with four events spanning the whole lifecycle โ real event data is never touched, under any circumstance, while it's on.
๐ก๏ธ Security, By Default
Nine non-negotiable rules, enforced everywhere โ not just where it was convenient.
Authorization is server-side, always. If it "works" only because a button is hidden client-side, it does not work.
Deadlines checked against server time, at the moment of the write โ never a client timestamp.
Nothing sensitive stored raw โ argon2 passwords, hashed session & invitation tokens.
Every privileged action is audit-logged โ role changes, invitations, publishing, admin bypasses.
Zero third-party hosted dependency, ever โ no cloud DB, no auth-as-a-service, no hosted CAPTCHA.
๐ Tiers Claimed โ Verified, Not Assumed
Flipped to true only once a tier's acceptance checks have actually
passed against the organizers' own run.py โ never claimed ahead of that.
$ python run.py .dogfood.toml --fixtures apps/api/prisma/fixtures.json
T1 gallery is public ................. PASS
T2 judge cannot see peer scores ...... PASS
claimed T1 T2, verified T1 T2
๐งฉ Built, Tested, Documented
Every module's stage doc ends with its own "what I'm testing" list โ treated as a minimum, not a suggestion.
Built by two teams,
shipped as one platform.
๐ฆ Thank you โ questions welcome.