NBA Game Day Notifications: Real-time Alerting System using AWS Services

MorolakeMorolake
4 min read

This project from Ifeanyi Otuonye focuses on creating an alerting system to deliver scores from sports games. I decided to go with NBA games since I enjoy watching Jokic in Denver. The services used include AWS SNS, AWS Lambda, AWS Event Bridge, IAM roles and policies, and the SportsData NBA API.

Architecture

The system queries the NBA API for scores, processes the data returned and sends it to our email via SNS. I used Terraform to provision resources on AWS to avoid using the console for projects like this.

Steps

  • Create an S3 bucket for the Terraform state

  • Create an SNS topic and subscription

  • Create IAM roles and policies for the SNS topic and Lambda function

  • Create the Lambda function

  • Create an EventBridge schedule

S3 bucket for Terraform state

I created an S3 bucket because I utilised an S3 backend and a pipeline to execute my Terraform commands as shown below.

resource "aws_s3_bucket" "game-day-notification-morolake" {
    bucket = "game-day-notification-morolake"
}
terraform {
    backend "s3" {
      bucket = "game-day-notification-morolake"
      key = "game-day-notification-morolake/terraform.tfstate"
      region = "us-east-1"
    }
}
name: Terraform CI/CD for Game Day Notification

on:
  push:
    branches:
      - master

jobs:
  terraform:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ secrets.AWS_REGION }}

    - name: Setup
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: 1.10.3

    - name: Initialise
      run: terraform init

    - name: Validate
      run: terraform validate

    - name: Plan
      run: terraform plan

    - name: Apply
      run: terraform apply -auto-approve

SNS topic and subscription

AWS SNS topics and subscriptions are core components used to facilitate a pub/sub messaging pattern. For this project, I used my email for the subscription endpoint. The depends_on argument is used to specify the dependency between resources and ensures that the SNS topic is created before the subscription.

resource "aws_sns_topic" "game-day-notification" {
    name = "game-day-notification"
}

resource "aws_sns_topic_subscription" "game-day-subscription" {
    topic_arn = "arn:aws:sns:us-east-1:<account-id>:game-day-notification"
    protocol = "email"
    endpoint = "morolaanney@gmail.com"
    depends_on = [aws_sns_topic.game-day-notification]
}

Confirm the email subscription

IAM Role and Policy

resource "aws_iam_policy" "game-day-iam-policy" {
    name = "game-day-iam-policy"
    path = "/"
    description = "Give permission to publish to SNS topic"

    policy = jsonencode ({
        Version: "2012-10-17",
        Statement: [
            {
                "Effect": "Allow",
                "Action": "sns:Publish",
                "Resource": "arn:aws:sns:us-east-1:<account-id>:game-day-notification"
            }
        ]
    })
}

resource "aws_iam_role" "game-day-lambda-role-new" {
    name = "game-day-lambda-role-new"
    managed_policy_arns = [aws_iam_policy.game-day-iam-policy.arn, "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]

    assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

Lambda Function

Retrieve the API key of your choice from SportsData.io and place that value in the NBA_API_KEY.

resource "aws_lambda_function" "game-day-lambda" {
    function_name = "game-day-lambda"
    runtime = "python3.13"
    role = aws_iam_role.game-day-lambda-role-new.arn
    filename = "notifications.zip"
    handler = "notifications.lambda_handler"

    environment {
        variables = {
            NBA_API_KEY = "api_key"
            SNS_TOPIC_ARN = "arn:aws:sns:us-east-1:<account-id>:game-day-notification"
        }
    }
}
import os
import json
import urllib.request
import boto3
from datetime import datetime, timedelta, timezone

def format_game_data(game):
    status = game.get("Status", "Unknown")
    away_team = game.get("AwayTeam", "Unknown")
    home_team = game.get("HomeTeam", "Unknown")
    final_score = f"{game.get('AwayTeamScore', 'N/A')}-{game.get('HomeTeamScore', 'N/A')}"
    start_time = game.get("DateTime", "Unknown")
    channel = game.get("Channel", "Unknown")

    # Format quarters
    quarters = game.get("Quarters", [])
    quarter_scores = ', '.join([f"Q{q['Number']}: {q.get('AwayScore', 'N/A')}-{q.get('HomeScore', 'N/A')}" for q in quarters])

    if status == "Final":
        return (
            f"Game Status: {status}\n"
            f"{away_team} vs {home_team}\n"
            f"Final Score: {final_score}\n"
            f"Start Time: {start_time}\n"
            f"Channel: {channel}\n"
            f"Quarter Scores: {quarter_scores}\n"
        )
    elif status == "InProgress":
        last_play = game.get("LastPlay", "N/A")
        return (
            f"Game Status: {status}\n"
            f"{away_team} vs {home_team}\n"
            f"Current Score: {final_score}\n"
            f"Last Play: {last_play}\n"
            f"Channel: {channel}\n"
        )
    elif status == "Scheduled":
        return (
            f"Game Status: {status}\n"
            f"{away_team} vs {home_team}\n"
            f"Start Time: {start_time}\n"
            f"Channel: {channel}\n"
        )
    else:
        return (
            f"Game Status: {status}\n"
            f"{away_team} vs {home_team}\n"
            f"Details are unavailable at the moment.\n"
        )

def lambda_handler(event, context):
    # Get environment variables
    api_key = os.getenv("NBA_API_KEY")
    sns_topic_arn = os.getenv("SNS_TOPIC_ARN")
    sns_client = boto3.client("sns")

    # Adjust for Central Time (UTC-6)
    utc_now = datetime.now(timezone.utc)
    central_time = utc_now - timedelta(hours=6)  # Central Time is UTC-6
    today_date = central_time.strftime("%Y-%m-%d")

    print(f"Fetching games for date: {today_date}")

    # Fetch data from the API
    api_url = f"https://api.sportsdata.io/v3/nba/scores/json/GamesByDate/{today_date}?key={api_key}"
    print(today_date)

    try:
        with urllib.request.urlopen(api_url) as response:
            data = json.loads(response.read().decode())
            print(json.dumps(data, indent=4))  # Debugging: log the raw data
    except Exception as e:
        print(f"Error fetching data from API: {e}")
        return {"statusCode": 500, "body": "Error fetching data"}

    # Include all games (final, in-progress, and scheduled)
    messages = [format_game_data(game) for game in data]
    final_message = "\n---\n".join(messages) if messages else "No games available for today."

    # Publish to SNS
    try:
        sns_client.publish(
            TopicArn=sns_topic_arn,
            Message=final_message,
            Subject="NBA Game Updates"
        )
        print("Message published to SNS successfully.")
    except Exception as e:
        print(f"Error publishing to SNS: {e}")
        return {"statusCode": 500, "body": "Error publishing to SNS"}

    return {"statusCode": 200, "body": "Data processed and sent to SNS"}

Test the lambda function after provisioning it, you should see a successful response.

Following that, check the email inbox you used for the SNS subscription and you should see an email containing the games played on the specified date alongside the scores from each quarter and the final scores.

I hope this was easy enough to understand, and here’s to hoping Joker gets a second ring. Find me on LinkedIn.

0
Subscribe to my newsletter

Read articles from Morolake directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Morolake
Morolake

Quality Assurance Analyst turned DevOps Engineer. Interested in all things Cloud Computing.