Real-time Planning Poker application built with Go, PocketBase, htmx, and Alpine.js. Features WebSocket-based real-time collaboration, persistent state management, and flexible access control.
- Real-time Collaboration: WebSocket-based instant updates across all participants
- Anonymous Access: No authentication required - create and join rooms instantly
- Flexible Voting: Support for Fibonacci, Modified Fibonacci, and custom value sets
- Role-Based Permissions: Voter and Spectator roles with configurable access controls
- Persistent State: SQLite database with automatic migrations and 24-hour room expiration
- Responsive UI: Clean, mobile-friendly interface built with htmx and Alpine.js
- Vote Statistics: Real-time calculation of averages and value distribution
# Start development environment with live reload
make dev
# Or start in background
make docker-up
# View logs
make docker-logs
# Stop
make docker-downApplication runs at http://localhost:8090
# Build binary
go build -o main .
# Run with automatic migrations
./main serve --http=0.0.0.0:8090
# Access application
open http://localhost:8090
# Access admin UI (database inspection)
open http://localhost:8090/_/Backend:
- PocketBase v0.30: All-in-one backend with Echo router, SQLite, and admin UI
- WebSocket Hub: Async broadcasting with per-client channels and fine-grained locking
- Connection Limits: 50 per room, 10,000 total with automatic capacity management
- Metrics & Monitoring: Real-time metrics at
/monitoring/metricsand health checks - State Management: Room state derived from current voting round
- Automatic Cleanup: Background job removes expired rooms hourly
Frontend:
- htmx 2.0: Declarative AJAX and WebSocket handling
- Alpine.js 3.14: Reactive UI components and state management
- Templ: Type-safe Go templating engine
Data Model:
rooms → rounds → votes
↓ ↓
└→ participants
rooms: Room configuration and metadata (24h TTL)rounds: Voting rounds with state (voting/revealed/completed)participants: Users with roles (voter/spectator) and connection statusvotes: Individual votes linked to participants and rounds
Client → Server:
vote: Cast or update a votereveal: Transition round to revealed state (show all votes)reset: Clear votes and return to voting statenext_round: Complete current round and start new oneupdate_name: Change participant nameupdate_room_name: Change room name (creator only)update_config: Update room permissions (creator only)
Server → Client:
room_state: Complete state sync on connect/reconnectparticipant_joined: User joined the roomparticipant_left: User left the roomvote_cast: Vote recorded (value hidden)vote_updated: Vote changed in revealed state (value shown)votes_revealed: All votes revealed with statisticsroom_reset: Voting round resetround_completed: New round startedname_updated: Participant name changedroom_name_updated: Room name changedconfig_updated: Room permissions updatedroom_expired: Room has expired (actions blocked)
Capacity (t3.micro - 1 vCPU, 1GB RAM):
- 2,000-3,000 concurrent rooms
- 20,000-30,000 WebSocket connections
- Handles 10-30x typical Planning Poker workload
Optimizations:
- Async broadcasting with non-blocking message delivery
- Per-client send channels (256 message buffer)
- Fine-grained locking with sync.Map
- Automatic slow client detection and cleanup
Monitoring:
# View real-time metrics
curl http://localhost:8090/monitoring/metrics | jq
# Health check
curl http://localhost:8090/monitoring/health- Origin Validation: Configurable WebSocket origin allowlist
- Rate Limiting: 10 messages per second per connection
- Connection Limits: Multi-level capacity management
- Input Sanitization: All user inputs validated and sanitized
- UUID Validation: All IDs validated before database operations
- Secure Cookies: Session cookies with secure flag (production)
- Message Validation: WebSocket message type and payload validation
- Docker & Docker Compose - For containerized development
- asdf - Version manager (recommended for consistent tooling)
- Make - Build automation
For consistent development environment across machines, use asdf to manage tool versions:
# Install asdf (if not already installed)
# macOS:
brew install asdf
# Linux:
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.18.0
# Install all project dependencies from .tool-versions
make asdf-installThis installs:
- Go 1.25.0 - Backend language
- Node.js 22.14.0 - For frontend tooling
- Templ 0.3.819 - Go templating engine
Verify installation:
asdf current # Check all tool versions
go version # Should show: go1.25.0
node --version # Should show: v22.14.0
templ version # Should show: v0.3.819Note: If templ is not found in PATH, add Go's bin directory to your shell configuration:
# Add to ~/.zshrc or ~/.bashrc
export PATH="$HOME/go/bin:$PATH"
# Reload shell
source ~/.zshrc # or ~/.bashrcplanning-poker/
├── main.go # Application entry point
├── pb_migrations/ # Database migrations
├── internal/
│ ├── models/ # Data models
│ ├── services/ # Business logic
│ ├── handlers/ # HTTP/WebSocket handlers
│ └── security/ # Validation and security
├── web/
│ ├── templates/ # Templ templates
│ └── static/ # CSS and JavaScript
└── tests/ # Integration tests
Migrations run automatically on startup (configurable via Automigrate: true in main.go).
Manual Migration Control:
# Check status
./main migrate collections
# Run migrations
./main migrate up
# Rollback last migration
./main migrate down 1Environment Variables:
# Disable automigrate
AUTOMIGRATE=false ./main serve
# Set data directory
./main serve --dir=/app/pb_data
# Configure WebSocket origins
WS_ALLOWED_ORIGINS=localhost:*,example.com:* ./main serve# Run tests
make test
# Run specific test
go test ./tests -v -run TestRoomCreationIntegration Test Coverage:
- Room creation and expiration
- Participant joining and role management
- Vote casting and statistics
- Round lifecycle (reveal, reset, next round)
- WebSocket connection and reconnection
- Permissions and access control
Automated deployment via GitHub Actions → AWS Systems Manager (SSM) → EC2:
- Infrastructure: Deploy with Terraform (see DEPLOY.md)
- Configure GitHub: Add repository variables (output after
terraform apply) - Deploy: Push git tag to trigger automated deployment
git tag v1.0.0
git push origin v1.0.0GitHub Actions automatically:
- Builds and pushes Docker image to GHCR
- Triggers SSM Run Command on EC2
- Pulls and deploys latest image with zero downtime
See DEPLOY.md for complete infrastructure setup guide.
Pre-built multi-architecture images are automatically published to GitHub Container Registry on each release.
Local Development/Testing:
# Pull and run latest image (with dev mode for WebSocket without SSL)
docker run -p 8090:8090 \
-v pb_data:/app/pb_data \
-e DEV_MODE=true \
-e WS_ALLOWED_ORIGINS=localhost:*,127.0.0.1:* \
--restart unless-stopped \
ghcr.io/damione1/planning-poker:latest
# Access application
open http://localhost:8090Production (Manual):
# Run specific version
docker run -p 8090:8090 \
-v pb_data:/app/pb_data \
-e DEV_MODE=false \
-e WS_ALLOWED_ORIGINS=yourdomain.com:* \
--restart unless-stopped \
ghcr.io/damione1/planning-poker:v1.0.0Using Docker Compose:
services:
planning-poker:
image: ghcr.io/damione1/planning-poker:latest
ports:
- "8090:8090"
volumes:
- pb_data:/app/pb_data
environment:
- DEV_MODE=false
- WS_ALLOWED_ORIGINS=yourdomain.com:*
restart: unless-stopped
volumes:
pb_data:# Build image locally
docker build -f Dockerfile -t planning-poker .
# Or build binary for Linux
GOOS=linux GOARCH=amd64 go build -o main .
./main serve --http=0.0.0.0:8090 --dir=/var/lib/pocketbaseDevelopment:
DEV_MODE=true
WS_ALLOWED_ORIGINS=localhost:*,127.0.0.1:*Production:
DEV_MODE=false
WS_ALLOWED_ORIGINS=yourdomain.com:*
AUTOMIGRATE=true