Skip to content

Latest commit

 

History

250 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BuddyBets

A peer-to-peer sports wagering platform — bet against your friends instead of the sportsbook.

Users register, add friends, and send each other head-to-head wagers on upcoming NBA games. Once both sides accept, the bet locks in. A cron job polls live scores every minute, settles finished games automatically, and moves credits between the two bettors without anyone having to touch it.

Built over the summer of 2023 as a learning project, with the deliberate goal of being backend-heavy: UTC/timezone handling, multi-table settlement queries, scheduled jobs, and cache invalidation.


Stack

Layer Technology
Frontend Angular 16, Angular Material, Bootstrap 5
Backend Node.js, Express 4
Database PostgreSQL (via pg)
Cache Redis
Auth JWT (jsonwebtoken) + bcrypt
Scheduling node-cron
External data API-Basketball via RapidAPI
Containers Docker + Docker Compose

Repository layout

BuddyBets/
├── backend/
│   ├── server.js               # Express entrypoint (port 3000)
│   ├── db.js                   # Postgres connection pool
│   ├── routes/                 # Route definitions per domain
│   │   ├── userrouting.js      # → /auth
│   │   ├── betsrouting.js      # → /bets
│   │   ├── baserouting.js      # → /route  (manual game/score refresh)
│   │   └── data-fromdb.js      # → /database (cached reads)
│   ├── controllers/            # Request handlers + settlement logic
│   ├── middleware/
│   │   ├── authorization.js    # JWT verification
│   │   └── validinfo.js        # Registration/login input validation
│   ├── modules/
│   │   ├── automation.js       # Cron schedules
│   │   └── datacaching.js      # RapidAPI fetch + filtering
│   ├── queries/queriesfile.js  # All parameterised SQL, in one place
│   ├── utils/                  # Date helpers, JWT generator
│   ├── Dockerfile
│   └── compose.yaml
├── frontend/angular/           # Angular SPA
├── IAC/main.tf                 # Terraform placeholder (currently empty)
└── dump.rdb                    # Redis snapshot

Getting started

Prerequisites

  • Node.js 18+
  • PostgreSQL 14+ running locally on 5432
  • Redis running locally on 6379
  • A RapidAPI key subscribed to API-Basketball

1. Database

The app expects a Postgres database with uuid-ossp enabled and tables for users, friends, games, bets, and betdetails. Table definitions live (commented) in backend/TABLES/TABLESTRUCTURE.js, and the full column set can be inferred from backend/queries/queriesfile.js.

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
  user_id  uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
  name     VARCHAR(255) NOT NULL,
  username VARCHAR(255) NOT NULL UNIQUE,
  email    VARCHAR(255) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL,
  credits  INTEGER DEFAULT 0
);

2. Environment

Create backend/.env:

dbname=wagering
password=your_postgres_password
JWTSECRET=your_jwt_signing_secret
APIKEY=your_rapidapi_key

The Postgres user and host are currently hardcoded in backend/db.js (logan @ localhost) — change them there or move them to env vars.

3. Backend

cd backend
npm install
npm run dev          # nodemon on :3000

4. Scheduled jobs

The cron jobs run as a separate process from the API server:

cd backend
npm run gameupdates

5. Frontend

cd frontend/angular
npm install
npm start            # ng serve on :4200

The Angular service layer points at http://localhost:3000 directly (see src/app/backendcalls.service.ts) — update this before deploying anywhere.

Docker

backend/compose.yaml brings up the API alongside a Postgres container. It reads the DB password from a Docker secret at backend/db/password.txt, which is gitignored — create it before running:

cd backend
mkdir -p db && echo "your_password" > db/password.txt
docker compose up --build

Note that Redis is not yet part of the Compose stack and must be running on the host.


How it works

Bet lifecycle

  1. Request — User A picks a game and an opponent, chooses a side and a wager. POST /bets/placebet writes a row to bets (the two participants and the game) and a linked row to betdetails (odds, wager, and which user took home vs away), joined by a foreign key on betid.
  2. Response — User B sees it under pending bets and either accepts (status → accepted) or denies (status → denied).
  3. Settlement — Every minute, getScoresController refreshes scores and status for in-progress games. DetermineWinners then runs a single SQL statement that joins betdetails → bets → games, compares the final score against each bettor's side, and stamps winnerid on every accepted bet whose game has status Game Finished and no winner yet. The RETURNING clause hands back exactly the rows it just settled, which are used to pay out credits and flip the bet to finished.

Doing the winner determination as one atomic UPDATE ... RETURNING avoids a read-then-write race and means the job is naturally idempotent — a bet that already has a winnerid is skipped on the next tick.

Game data and caching

The RapidAPI basketball endpoint only accepts a single date per call, so GamesForNext7DaysCall fans out seven parallel requests via Promise.all, then filters the combined response down to league IDs 12 and 13. Filtering client-side across all leagues in one call per day uses fewer API credits than querying per-league.

Every day at 00:10 UTC, getGamesForDay pulls in the newly-visible seventh day, keeping a rolling 7-day forecast in Postgres, then writes the full week into Redis under the games key. The frontend reads that cache through GET /database/GetWeekGames rather than hitting Postgres on every page load.

All date arithmetic is done in UTC deliberately — the upstream API rolls over its schedule on UTC days, so working in local time produced off-by-one-day errors on games near midnight.


API reference

Base URL: http://localhost:3000. Endpoints marked 🔒 require a valid JWT in the token header; the token payload carries the user's UUID, so no user ID is sent in the request body.

Auth & users — /auth

Method Endpoint Description
POST /auth/register Creates a user with a bcrypt-hashed password. Requires name, email, username, password. Returns a JWT.
POST /auth/login Validates credentials, returns a JWT or the relevant error.
GET /auth/is-verify 🔒 Returns true if the supplied token is valid.
GET /auth/dashboard 🔒 Returns the authenticated user's dashboard data.
GET /auth/getuserid 🔒 Resolves the token to a user UUID.
GET /auth/getfriends 🔒 Returns the user's friends, with usernames resolved from UUIDs.
POST /auth/newfriendrequest 🔒 Takes a target username, stores both UUIDs in friends with pending status. Rejects duplicates in either direction.
POST /auth/acceptfriendrequest Takes a request ID, sets status to accepted.
POST /auth/denyfriendrequest Takes a request ID, deletes the row.

Bets — /bets

Method Endpoint Description
POST /bets/placebet 🔒 Creates the bet and its details. Takes opponent ID, wager, odds, and game ID.
GET /bets/getpendingbetsreceived 🔒 Incoming bet requests awaiting the user's response.
GET /bets/getpendingbetssent 🔒 Outgoing requests awaiting the opponent.
GET /bets/getongoingbets 🔒 Accepted bets on games that haven't settled yet.
POST /bets/acceptbet 🔒 Sets bet status to accepted.
POST /bets/denybet Sets bet status to denied.

Game data — /database

Method Endpoint Description
GET /database/GetWeekGames Returns the cached 7-day slate from Redis.
GET /database/DockerTest Container health check.

Manual refresh — /route

Normally driven by cron; exposed for debugging and backfill.

Method Endpoint Description
GET /route/updategames-database Full game refresh.
GET /route/updategamestoday-database Rolls the 7-day window forward and re-caches.
GET /route/updatescores-database Pulls current scores and game statuses.

Scheduled jobs

Defined in backend/modules/automation.js.

Schedule Job Purpose
10 0 * * * (UTC) getGamesForDay Extends the rolling 7-day forecast and refreshes the Redis cache.
* * * * * getScoresControllerDetermineWinners Updates live scores, then settles any newly-finished games and distributes credits.

Frontend routes

Path Component
/ Register
/login Login
/dashboard Dashboard
/gamesdisplay Upcoming games
/betpage/:gameid Place a bet on a specific game
/friends Add friends / manage requests
/friendsdisplay Friends list
/pendingbets Sent and received bet requests
/ongoingbets Active and completed bets

Routes are protected client-side by loginguard.guard.ts, which calls /auth/is-verify.


Known gaps

Honest list of what a second pass would address:

  • Secrets and config — DB user/host and the frontend's API base URL are hardcoded; both should come from environment config.
  • Committed artifactsdump.rdb (a Redis snapshot) is tracked in git and should be gitignored.
  • Redis in Compose — the Compose stack covers the API and Postgres only; Redis still has to run on the host.
  • Missing auth on two endpoints/bets/denybet, /auth/acceptfriendrequest, and /auth/denyfriendrequest accept a raw record ID without verifying the caller owns it.
  • Schema as code — table definitions live in comments rather than a migration tool.
  • TerraformIAC/main.tf is an empty placeholder.
  • Tests — Angular spec files are CLI-generated stubs; there's no backend test suite.

Disclaimer

Built as a personal learning project. Wagering is simulated with in-app credits — there is no real money, no payment processing, and no compliance or age-verification tooling. Not intended for production use.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages