Sports Tech · Social

PredictHubSports Prediction Marketplace

Lead Full-Stack Engineer — Sole Engineer

PredictHub

Pre-launchScreenshots coming soon

Overview

PredictHub is a sports prediction marketplace where football fans publish match picks (win/draw/BTTS/over-under), build a public reputation, and earn from being right. Predictions are tied to live matches sourced from The Odds API and auto-resolved by a cron job every 30 minutes. Users follow top predictors, compete on a global leaderboard, and interact through a social feed — the feel of Twitter-for-tipsters. A wallet system handles subscription payments and withdrawals. An AI scoring module (Claude API) evaluates prediction quality. I was the sole engineer — responsible for architecture, all three apps, database schema, auth flow, and deployment.

The Hard Architectural Problem

Supabase RLS assumes auth.uid() comes from a Supabase session token — but the app issues its own JWTs via NestJS Passport, making auth.uid() always null and silently blocking every write and owner-scoped read.

The fix was a deliberate architectural rule enforced at the service layer: all DB calls go through the service-role admin client (bypassing RLS entirely), while RLS remains active only at the edge for any direct client access. This eliminates the auth.uid() null problem without disabling RLS globally — a clean boundary that keeps the security model intact.

A second class of bug: PostgREST ambiguous foreign key paths across junction tables (predictions ↔ users) required explicit FK hints on every embed query. These fail silently at query time with opaque 406 errors — found and fixed by reading PostgREST source behaviour, not the docs.

Architecture Pattern

NestJS Passport JWT · Supabase RLS bypass via service-role admin client · PostgREST explicit FK hints · 30-min cron result polling · OTP-gated auth via Resend API

What I Built

01

OTP-Gated Authentication

  • Register and login both require email OTP verification before a JWT is issued — no password stored.
  • Welcome email sent on first verify via Resend transactional API (6-digit OTP, 10-min expiry).
  • NestJS Passport strategy validates the custom JWT on every protected route.
02

Prediction Engine & Cron Resolution

  • Users create picks tied to live matches fetched from The Odds API (match existence validated on creation).
  • A 30-minute cron job polls match results and auto-resolves open predictions — win, loss, or void.
  • Prediction history, win rates, and streaks update in real time after each resolution cycle.
03

Social Feed, Likes & Comments

  • Paginated activity feed showing predictions from followed users and trending picks.
  • Likes, comments, and nested replies with optimistic UI updates on the frontend.
  • Follow/unfollow system with follower counts and mutual-follow detection.
04

Wallet & Transaction System

  • Wallet auto-created on user signup with zero balance.
  • Deposit, withdraw, and subscription payment flows with full transaction history.
  • Admin approval gate on withdrawal requests before funds are released.
05

Leaderboard & Referral Program

  • Global leaderboard ranked by win rate and prediction volume, filterable by sport.
  • Invite-only referral system — links tracked to referrer with conversion metrics surfaced in admin.
06

Admin Dashboard

  • Separate authenticated Vite app (port 5174) with its own auth boundary.
  • User moderation: verify, ban, flag accounts; view full prediction and transaction history per user.
  • Withdrawal approval workflow, transaction oversight, and overview charts (Recharts).
  • Referral conversion metrics and invite analytics.
07

AI Prediction Scoring Module

  • Scaffolded Claude API integration to evaluate prediction quality against historical odds and outcomes.
  • Pluggable architecture — the scoring module runs as an independent service, callable by the cron pipeline.
  • Designed to surface a quality score per predictor, surfaced on the leaderboard.

Database Architecture

13 migration files across a normalised PostgreSQL schema. Key design decisions:

  • Junction tables for predictions ↔ matches and users ↔ followers — explicit FK hints on all PostgREST embeds.
  • Row-level security policies defined per table, enforced only at the edge (direct client access).
  • Service-role admin client used exclusively at the NestJS service layer — no RLS friction on writes.
  • Wallet and transaction tables fully separated from user auth — allows independent balance reconciliation.

Business Impact

0→prod

in one build sprint

<2s

OTP email delivery

13

DB migration files

  • All core flows verified end-to-end — zero TypeScript errors, zero RLS leakage on clean Render deploy.
  • OTP email delivered via Resend in under 2s average — no auth friction at onboarding.
  • Cron match resolution runs 8 result checks per day, within The Odds API free tier with headroom.
  • Full admin moderation and payment workflow in place — no manual DB access needed for operations.

Tech Stack

React 18TypeScriptViteReact Router v6TanStack Query v5NestJS v11Supabase (PostgreSQL)Passport JWTShadcn UITailwind CSSRechartsThe Odds APIResend APIClaude APIRenderVercel
Caleb Benjamin — Senior Frontend Engineer