A production-ready Go application for managing on-call schedules and rotations. It provides a RESTful API for creating schedules, tracking rotations, and querying current on-call members.
- PostgreSQL Storage: Production-ready persistence with full rotation tracking
- In-Memory Storage: Lightweight option for development and testing
- Proper rotation tracking with position state management
- Support for multiple schedules per team
- Flexible weekday and time range configuration
- Timezone-aware time comparisons
- User and team management
- Database migrations for schema versioning
- Structured logging with Zap
- Graceful shutdown handling
- Configuration via YAML file or environment variables
The application can be configured using either a YAML file or environment variables.
Create a config.yaml file (default included in the repository):
server:
address: "0.0.0.0"
port: 1373
database:
host: "localhost"
port: 5432
user: "oncall"
password: "oncall"
database: "oncall"
ssl_mode: "disable"
max_connections: 10
min_connections: 2
migrations_path: "migrations"Configuration can be overridden using environment variables with the ONCALL_ prefix:
# Server configuration
export ONCALL_SERVER__ADDRESS=localhost
export ONCALL_SERVER__PORT=8080
# Database configuration
export ONCALL_DATABASE__HOST=localhost
export ONCALL_DATABASE__PORT=5432
export ONCALL_DATABASE__USER=oncall
export ONCALL_DATABASE__PASSWORD=oncall
export ONCALL_DATABASE__DATABASE=oncall
# Storage mode (set to false to use in-memory storage)
export ONCALL_USE_DATABASE=trueNote: Use double underscores (__) to represent nested configuration keys.
Server:
- Address:
0.0.0.0 - Port:
1373
Database:
- Host:
localhost - Port:
5432 - User:
oncall - Password:
oncall - Database:
oncall - SSL Mode:
disable - Max Connections:
10 - Min Connections:
2
Install just command runner:
# macOS
brew install just
# Linux
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
# Or with cargo
cargo install just- Start PostgreSQL using Docker Compose:
just db-upOr manually:
docker compose up -d postgres- Build and run the application:
just build
./bin/oncall-scheduleOr run directly:
just runjust run-memoryOr:
ONCALL_USE_DATABASE=false go run .The API will be available at http://localhost:1373 (or your configured address/port).
Run just or just --list to see all available commands:
# Essential Commands
just build # Build the application
just run # Run with PostgreSQL
just run-memory # Run with in-memory storage
just test # Run tests with coverage
just test-coverage # Run tests and open coverage in browser
# Database Commands
just db-up # Start PostgreSQL
just db-down # Stop PostgreSQL
just db-reset # Reset database (deletes all data)
just db-shell # Connect to PostgreSQL with psql
just db-logs # View PostgreSQL logs
# Development Commands
just dev # Watch for changes and auto-reload
just lint # Run linter
just lint-fix # Fix linting issues automatically
just fmt # Format code
just tidy # Tidy dependencies
# Docker Commands
just docker-build # Build Docker image
just docker-up # Start all services
just docker-down # Stop all services
just docker-logs # View all service logs
# Utilities
just clean # Clean build artifacts
just clean-all # Clean everything including Docker volumes
just ci # Run full CI pipeline locally
just stats # Show project statistics
just install-tools # Install development toolsCreate a new on-call schedule for a team.
Endpoint: POST /schedule
Request Body:
{
"name": "Weekend Coverage",
"team": "backend-team",
"members": ["Alice", "Bob", "Charlie"],
"days": ["Saturday", "Sunday"],
"start": "9:00AM",
"end": "5:00PM"
}Fields:
name(string, required): Schedule name/identifierteam(string, required): Team identifiermembers(array, required): List of team members in the rotation (must not be empty)days(array, required): Weekdays when this schedule applies (case-insensitive: "Monday", "Tuesday", etc.)start(string, required): Start time in 12-hour format (e.g., "9:00AM", "1:30PM")end(string, required): End time in 12-hour format (must be after start time)
Response:
201 Createdon success400 Bad Requestwith error details on validation failure
Example:
curl -X POST http://localhost:1373/schedule \
-H "Content-Type: application/json" \
-d '{
"name": "Weekday Shift",
"team": "ops-team",
"members": ["John", "Jane", "Joe"],
"days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"start": "9:00AM",
"end": "5:00PM"
}'Retrieve the currently on-call member for a team at a specific time.
Endpoint: GET /schedule
Query Parameters:
team(string, required): Team identifiertime(string, required): RFC3339 formatted timestamp (e.g., "2025-04-26T09:00:00Z")
Response:
200 OKwith current oncall member:{"oncall": "John"}404 Not Foundif no schedule matches the query (wrong team, day, or time outside schedule window)400 Bad Requestif parameters are missing or invalid
Example:
curl "http://localhost:1373/schedule?team=ops-team&time=2025-04-28T14:30:00Z"Response:
{
"oncall": "John"
}Note: With PostgreSQL storage, this returns the currently on-call person based on rotation state. With in-memory storage, it returns the first member in the rotation.
The application uses a relational database schema with the following key tables:
- users: Stores user information (username, email, phone, Slack ID)
- teams: Team definitions
- team_members: Many-to-many relationship between teams and users
- schedules: Schedule definitions with time windows and team associations
- schedule_days: Which days of the week each schedule applies to
- schedule_members: Members in rotation for each schedule (with position tracking)
- rotations: Current rotation state for each schedule (tracks who's currently on-call)
- schedule_overrides: Temporary coverage changes (future feature)
- incidents: Incident tracking (future feature)
- incident_timeline: Activity log for incidents (future feature)
- Validates all required fields are present and non-empty
- Parses weekday strings (case-insensitive)
- Parses start/end times in 12-hour format
- Validates start time is before end time
- Creates or retrieves team from database
- Creates or retrieves users for each member
- Creates schedule with time windows and days
- Assigns members to schedule with rotation positions
- Initializes rotation state (starts at position 0)
- Validates team and time parameters
- Parses RFC3339 timestamp
- Queries database for matching schedule:
- Team matches
- Day of week matches
- Time falls within schedule window
- Returns currently on-call member based on rotation state
- Uses timezone-aware time comparisons
The rotation system tracks:
- Current position: Index into the members list
- Current user: Who is currently on-call
- Last rotation time: When the last rotation occurred
- Next rotation time: When the next rotation should happen (future feature)
The PostgreSQL storage implementation properly tracks rotation state, ensuring that the same person stays on-call until manually rotated. The in-memory storage always returns the first member (simplified implementation).
oncall-schedule/
├── main.go # Application entry point with FX dependency injection
├── config.yaml # Default configuration
├── docker-compose.yml # Modern Docker Compose setup for PostgreSQL
├── justfile # Just command runner recipes
├── migrations/ # Database migration files
│ ├── 000001_initial_schema.up.sql
│ └── 000001_initial_schema.down.sql
└── internal/
├── config/ # Configuration loading (YAML + env vars)
│ └── config.go
├── db/ # Database connection and migrations
│ └── db.go
├── handler/ # HTTP request handlers
│ ├── handler.go
│ └── handler_test.go
└── storage/ # Storage interface and implementations
├── storage.go # Interface and in-memory implementation
├── storage_test.go
└── postgres.go # PostgreSQL implementation
- Language: Go 1.24+
- Web Framework: Echo v4
- Database: PostgreSQL with pgx driver
- Migrations: golang-migrate
- Dependency Injection: Uber FX
- Logging: Uber Zap
- Configuration: Koanf (YAML + environment variables)
- Repository Pattern: Storage interface with multiple implementations
- Dependency Injection: Clean separation of concerns using Uber FX
- Clean Architecture: Clear boundaries between layers (handler, storage, db)
- Configuration Management: 12-factor app approach with environment variables
This project is on a path to become a production-ready oncall platform. Here's what's implemented and what's planned:
- PostgreSQL database layer with migrations
- Proper rotation tracking with state management
- User and team management (database schema)
- Modern Docker Compose setup (v2 specification)
- Just command runner with comprehensive recipes
- Configuration management
- Structured logging
- Unit tests (93.7% coverage on handler, 100% on storage)
- User contact information management API
- Email notification system (SMTP/SendGrid)
- Slack integration (webhooks + bot)
- SMS notifications (Twilio)
- Alert webhook endpoint
- Alert routing to current oncall person
- Notification delivery tracking
- Incident creation and tracking
- Incident acknowledgment workflow
- Incident resolution workflow
- Escalation policies (auto-escalate if not acknowledged)
- Multi-level escalation chains
- Schedule override API (vacation/PTO handling)
- Shift swapping between team members
- JWT-based authentication
- API key support for integrations
- Role-based access control (RBAC)
- Complete REST API (update/delete operations)
- API pagination and filtering
- OpenAPI/Swagger documentation
- Prometheus metrics
- Health check endpoints
- Rate limiting
- HTTPS/TLS configuration
- Dockerfile and Kubernetes manifests
- Unit tests for all packages
- Integration tests for database operations
- API endpoint tests
- Load testing
- Comprehensive API documentation
- Deployment guide
- Operations runbook
- Web UI for schedule management
- Mobile apps (iOS/Android)
- Calendar integrations (Google Calendar, Outlook)
- Advanced analytics and reporting
- Postmortem workflow
- Multi-region support
- SSO integration
This is a learning project, but contributions and suggestions are welcome! Feel free to:
- Open issues for bugs or feature requests
- Submit pull requests
- Share feedback on the architecture
- Suggest improvements to the roadmap
Apache License 2.0 - See LICENSE file for details.