← All projects
Case study · Full-stack web app · Live

BetTail

A live web app where private groups share sports picks, follow them with their own stakes and track profit and leaderboards. Access control lives in the database: every request runs as the signed-in user under row-level security, and the app has no admin database key.

Status
Live, private source
Timeline
September 2026 – present
Stack
Next.js, React, TypeScript, Zod
Works with
Supabase (PostgreSQL, Auth, Realtime), Vercel
BetTail's public landing page. Product previews use sample data.
The public landing page. Previews use sample data, never members' records.
BetTail on a phone-sized screen
Built phone-first; it installs as a web app.

Key decisionPut authorization in the database, not the app. Writes go through database functions that read the user from the session, so a direct API call gets the same checks the app does.

What it shows

Access control in the database

Row-level security on every exposed table, direct client writes denied, and default privileges revoked so new tables start private.

Money records that add up

Amounts in integer cents, profit calculated in SQL, and a receipt on every write so a retried request can't double-post.

Private live updates

Realtime messages carry no data. Clients reload through a membership check, so a removed member can't receive the group's records.

Tests on the real schema

Database tests run the actual migrations in embedded PostgreSQL, so roles, grants and row-level security are tested directly instead of mocked.

How it works

Friend groups usually share bets as screenshots in a group chat, which makes it hard to see who's up and what's still pending. BetTail gives each group one shared record. A member posts a pick, others follow it with their own stake and odds, and the database calculates profit, ROI and leaderboards.

Follow a request Choose a stage

  1. 01Member action
  2. 02Request checks
  3. 03Database write
  4. 04Live update

Stage 01 / Member action

What is the browser asking for?

A member posts a pick by typing it, pasting a sportsbook link or uploading a screenshot. Numbers read from a screenshot need matching ticket text, and every inferred field stays editable.

Boundary

The browser never decides ownership or money totals.

Stage 02 / Request checks

Who is asking, and is the request sane?

The server checks the session, the request's origin and its size. Upstream calls time out after 10 seconds, and writes are never retried automatically.

Boundary

These checks sit in front of the database's own authorization, not in place of it.

Stage 03 / Database write

Is this member still allowed to do this?

Guarded SQL functions take the user from the session, check group membership, and write inside a transaction. PostgreSQL limits each user to 8 invite-code guesses and 60 writes a minute.

Boundary

More logic in SQL means every migration needs review and real database tests.

Stage 04 / Live update

What leaves the database?

A realtime message only says that something changed. Each client reloads through a query that checks membership again.

Boundary

Per-user limits stop one abusive account, not a distributed attack.

What my review found

I reviewed every migration, route, live permission and sign-in setting. Each finding below has a fix and a regression test.

Security review findings, what happened before the fix, and what happens now
FindingBefore the fixNow
NULL flagA database function accepted SQL NULL for a flag, skipping its cache-only branchNULL handled explicitly; a test proves nothing is queued
Write frequencyNewer functions had size caps but no per-user rate limitRate limits enforced in PostgreSQL, so direct API calls are covered too
Future tablesNew tables could inherit app-role privilegesDefault privileges revoked, verified in a rolled-back live transaction
Session cookiesDidn't explicitly require HTTPSSecure, SameSite=Lax, host-only cookies everywhere
CSV exportFormula protection missed cells starting with a space or control characterEvery formula-like cell neutralized before quoting
Backend callsNo deadline10-second timeout; writes never retried automatically

AI research reports

The NFL tab shows reports from Downfield, my local AI research agent. It publishes through its own short-lived, report-only credential, so it never gets a member's login or any database access. Live sportsbook prices come from server-only code; the price-feed API keys live in Vercel's server environment and never reach the browser or the agent. BetTail tracks picks; it doesn't place bets.

Limits

Discuss BetTail

Ask about row-level security, the database functions behind every write or how the AI reports are published. Email me at aagarwalcollege@gmail.com.

Project screenshot

Open original ↗

View full size to read details. Scroll or swipe to move around the enlarged image.