-
Notifications
You must be signed in to change notification settings - Fork 0
Developer Guide
github-actions[bot] edited this page Mar 14, 2026
·
1 revision
Complete guide for developers contributing to or extending BetTrack.
- Development Environment
- Project Structure
- Contributing Workflow
- Code Standards
- Testing Strategy
- Build & Release
- Debugging
- Common Tasks
- Git - Version control
- Python 3.11+ - MCP server development
- Node.js 20+ - Dashboard development
- PostgreSQL 15+ - Database
- Docker - Container testing (optional)
- VS Code - Recommended IDE
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"Prisma.prisma",
"bradlc.vscode-tailwindcss",
"formulahendry.auto-rename-tag"
]
}git clone https://github.com/WFord26/BetTrack.git
cd BetTrackBetTrack/
├── mcp/ # MCP Server (Python)
│ ├── sports_mcp_server.py # Main server
│ ├── dashboard_mcp_server.py # Dashboard integration server
│ ├── sports_api/ # API handlers
│ ├── releases/ # Built MCPB packages
│ └── tests/ # Tests (TODO)
├── dashboard/ # Web Dashboard
│ ├── backend/ # Node.js + TypeScript
│ │ ├── src/
│ │ │ ├── routes/ # Express routes
│ │ │ ├── services/ # Business logic
│ │ │ ├── jobs/ # Cron jobs
│ │ │ └── middleware/ # Express middleware
│ │ ├── prisma/ # Database schema
│ │ └── tests/ # Jest tests
│ └── frontend/ # React + Vite
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── store/ # Redux store
│ │ ├── hooks/ # Custom hooks
│ │ └── pages/ # Page components
│ └── tests/ # Vitest tests
├── scripts/ # Build scripts
│ ├── build.ps1 # Main build script
│ └── docker-build.ps1 # Docker builds
├── docs/ # Documentation
│ ├── wiki/ # GitHub wiki pages
│ └── *.md # Various docs
└── .github/ # GitHub Actions
├── workflows/ # CI/CD pipelines
└── copilot-instructions.md # AI agent guide
# Fork repository on GitHub
# Clone your fork
git clone https://github.com/YOUR_USERNAME/BetTrack.git
cd BetTrack
# Add upstream remote
git remote add upstream https://github.com/WFord26/BetTrack.git
# Create feature branch
git checkout -b feature/your-feature-nameFollow the Code Standards section below.
# MCP Server (manual testing via Claude)
python mcp/sports_mcp_server.py
# Backend tests
cd dashboard/backend
npm test
# Frontend tests
cd dashboard/frontend
npm test# Build MCP package
cd scripts
.\build.ps1 -VersionBump patch
# Build dashboard
.\build.ps1 -Dashboard -BumpBackend -BumpFrontendCRITICAL: Always build before pushing to catch errors!
# Stage changes
git add .
# Commit with descriptive message
git commit -m "feat: add player prop filtering to odds API"
# Push to your fork
git push origin feature/your-feature-name- Go to GitHub and create PR from your fork
- Fill out PR template
- Link related issues
- Wait for CI/CD checks
- Address review comments
Style Guide: PEP 8
# Good
async def get_odds(sport: str, markets: str = "h2h") -> dict:
"""
Fetch betting odds for a sport.
Args:
sport: Sport key (e.g., "basketball_nba")
markets: Comma-separated markets (default: "h2h")
Returns:
dict: {"success": bool, "data": [...], "error": str}
"""
try:
result = await odds_handler.get_odds(sport, markets)
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}Key Points:
- Use type hints for all function parameters
- Document functions with docstrings
- Always return dict with
{"success": bool, ...}structure - Use async/await for all API calls
- Handle exceptions gracefully
Style Guide: Airbnb TypeScript
// Good
export class GameService {
private prisma: PrismaClient;
constructor() {
this.prisma = new PrismaClient();
}
/**
* Find games with timezone-aware date filtering
*/
async findGames(filters: GameFilters): Promise<Game[]> {
const { sport, startDate, endDate } = filters;
return this.prisma.game.findMany({
where: {
sport,
commenceTime: {
gte: startDate,
lt: endDate,
},
},
include: {
homeTeam: true,
awayTeam: true,
},
});
}
}Key Points:
- Use TypeScript strict mode
- Define interfaces for all data structures
- Use service layer pattern (separate business logic)
- Document public methods with JSDoc
- Use async/await, avoid callbacks
- Handle errors with try/catch
Style Guide: React + Hooks
// Good
interface GameCardProps {
game: Game;
onBetClick?: (bet: BetData) => void;
}
export function GameCard({ game, onBetClick }: GameCardProps) {
const dispatch = useDispatch();
const [isExpanded, setIsExpanded] = useState(false);
const handleAddBet = useCallback((betType: string, odds: number) => {
dispatch(addBet({ gameId: game.id, betType, odds }));
onBetClick?.({ gameId: game.id, betType, odds });
}, [dispatch, game.id, onBetClick]);
return (
<div className="card">
{/* Component JSX */}
</div>
);
}Key Points:
- Use functional components with hooks
- Define TypeScript interfaces for props
- Use
useCallbackfor event handlers - Prefer named exports over default exports
- Use Tailwind CSS classes, avoid inline styles
- Keep components focused (single responsibility)
Convention: Conventional Commits
# Format
<type>(<scope>): <subject>
# Types
feat: New feature
fix: Bug fix
docs: Documentation only
style: Formatting, missing semicolons
refactor: Code restructuring
test: Adding tests
chore: Build process, dependencies
# Examples
feat(mcp): add player props filtering
fix(backend): resolve timezone calculation bug
docs(wiki): update installation guide
chore(deps): upgrade Prisma to 5.8.0# Structure
mcp/tests/
├── test_odds_api_handler.py
├── test_espn_api_handler.py
├── test_formatters.py
└── test_team_reference.py
# Run tests (when implemented)
cd mcp
pytest tests/ -vcd dashboard/backend
# Run all tests
npm test
# Watch mode
npm run test:watch
# Coverage
npm run test:coverageExample Test:
// tests/services/game.service.test.ts
import { GameService } from '../../src/services/game.service';
describe('GameService', () => {
let service: GameService;
beforeEach(() => {
service = new GameService();
});
describe('findGames', () => {
it('should return games for specified sport', async () => {
const games = await service.findGames({
sport: 'basketball_nba',
});
expect(games).toBeDefined();
expect(games.every(g => g.sport === 'basketball_nba')).toBe(true);
});
});
});cd dashboard/frontend
# Run tests
npm test
# Watch mode
npm run test:watch
# UI mode
npm run test:uiExample Test:
// tests/components/GameCard.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { GameCard } from '../../src/components/GameCard';
describe('GameCard', () => {
const mockGame = {
id: '1',
homeTeam: 'Lakers',
awayTeam: 'Celtics',
homeOdds: -150,
awayOdds: 130,
};
it('renders team names', () => {
render(<GameCard game={mockGame} />);
expect(screen.getByText('Lakers')).toBeInTheDocument();
expect(screen.getByText('Celtics')).toBeInTheDocument();
});
it('calls onBetClick when odds button clicked', () => {
const handleBetClick = jest.fn();
render(<GameCard game={mockGame} onBetClick={handleBetClick} />);
fireEvent.click(screen.getByText('-150'));
expect(handleBetClick).toHaveBeenCalledWith({
gameId: '1',
team: 'Lakers',
odds: -150,
});
});
});cd scripts
# MCP server only
.\build.ps1 -VersionBump patch
# Dashboard only
.\build.ps1 -Dashboard -BumpBackend -BumpFrontend
# Everything
.\build.ps1 -Dashboard -VersionBump patch -BumpBackend -BumpFrontend
# Beta build (testing)
.\build.ps1 -Beta# Full release (bumps version, creates GitHub release)
.\build.ps1 -VersionBump minor -Release
# What happens:
# 1. Version bumped in manifest.json and package.json
# 2. Git tag created (e.g., v0.2.0)
# 3. MCPB package built
# 4. GitHub release created with changelog
# 5. Tag pushed to GitHub.\docker-build.ps1 -Version "2026.01.12" -Backend -Frontend -Push
# Builds:
# - ghcr.io/wford26/bettrack-backend:2026.01.12
# - ghcr.io/wford26/bettrack-frontend:2026.01.12
# - Both tagged as :latestVS Code Launch Configuration (.vscode/launch.json):
{
"version": "0.2.0",
"configurations": [
{
"name": "MCP Server",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/mcp/sports_mcp_server.py",
"console": "integratedTerminal",
"env": {
"ODDS_API_KEY": "your_key_here"
}
}
]
}Logging:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug(f"Fetching odds for {sport}")VS Code Configuration:
{
"name": "Backend (Node)",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"cwd": "${workspaceFolder}/dashboard/backend",
"console": "integratedTerminal"
}Logging (Winston):
import { logger } from './utils/logger';
logger.info('Starting odds sync', { sport: 'basketball_nba' });
logger.error('API request failed', { error: err.message });Browser DevTools:
- Console: Check for errors and logs
- Network: Monitor API requests
- Redux DevTools: Inspect state changes
- React DevTools: Component hierarchy
Console Logging:
console.log('[GameCard] Rendering with game:', game);
console.error('[API] Request failed:', error);# Prisma Studio (visual database browser)
cd dashboard/backend
npm run prisma:studio
# Raw SQL queries
npm run prisma:studio
# Or use psql:
psql -U postgres -d bettrackFile: mcp/sports_mcp_server.py
@mcp.tool()
async def get_team_roster(team_name: str, sport: str) -> dict:
"""
Get roster for a team.
Args:
team_name: Team name (e.g., "Lakers")
sport: Sport key (e.g., "basketball_nba")
Returns:
{"success": bool, "data": [...], "error": str}
"""
try:
team_id = find_team_id(team_name, sport)
if not team_id:
return {"success": False, "error": "Team not found"}
roster = await espn_handler.get_roster(sport, team_id)
return {"success": True, "data": roster}
except Exception as e:
return {"success": False, "error": str(e)}File: dashboard/backend/src/routes/teams.routes.ts
import { Router } from 'express';
import { TeamService } from '../services/team.service';
const router = Router();
const teamService = new TeamService();
router.get('/:id', async (req, res, next) => {
try {
const team = await teamService.findById(req.params.id);
if (!team) {
return res.status(404).json({ error: 'Team not found' });
}
res.json(team);
} catch (error) {
next(error);
}
});
export default router;Register in app.ts:
import teamsRoutes from './routes/teams.routes';
app.use('/api/teams', teamsRoutes);File: dashboard/frontend/src/components/TeamCard.tsx
import { Team } from '../types';
interface TeamCardProps {
team: Team;
}
export function TeamCard({ team }: TeamCardProps) {
return (
<div className="card">
<img src={team.logoUrl} alt={team.name} className="w-16 h-16" />
<h3 className="font-bold text-lg">{team.name}</h3>
<p className="text-gray-600">{team.abbr}</p>
</div>
);
}File: dashboard/backend/prisma/schema.prisma
model Player {
id String @id @default(uuid())
name String
position String
teamId String
team Team @relation(fields: [teamId], references: [id])
stats Json?
@@index([teamId])
@@map("players")
}
model Team {
id String @id @default(uuid())
name String
players Player[]
}Create Migration:
npm run prisma:migrate -- --name add_players
npm run prisma:generateLocation: Component-specific changelogs
## [Unreleased]
### Added
- New player roster API endpoint (#123)
- Team card component with logo display
- Player props filtering in odds API
### Changed
- Updated odds sync to run every 5 minutes (was 10)
- Improved timezone handling in game queries
### Fixed
- Fixed bet slip not clearing after submission (#125)
- Resolved duplicate team entries in database# Use async/await
async def fetch_multiple_sports():
results = await asyncio.gather(
odds_handler.get_odds("basketball_nba"),
odds_handler.get_odds("americanfootball_nfl"),
odds_handler.get_odds("icehockey_nhl")
)
return results
# Reuse sessions
class OddsAPIHandler:
async def _get_session(self):
if self._session is None:
self._session = aiohttp.ClientSession()
return self._session// Use database indexes
@@index([sport, commenceTime])
// Batch database operations
await prisma.bet.createMany({ data: bets });
// Select only needed fields
const games = await prisma.game.findMany({
select: { id: true, homeTeam: true, awayTeam: true },
});// Use React.memo for expensive components
export const GameCard = React.memo(({ game }) => {
// ...
});
// Use useCallback for event handlers
const handleClick = useCallback(() => {
// ...
}, [deps]);
// Lazy load heavy components
const OddsChart = lazy(() => import('./OddsChart'));Happy coding! 🚀