diff --git a/wow-token-updater.py b/wow-token-updater.py index 4dc7ad0..88638c2 100644 --- a/wow-token-updater.py +++ b/wow-token-updater.py @@ -6,39 +6,68 @@ import os import requests -dynamo_client = boto3.client('dynamodb', region_name='us-east-1') -timestream_client = boto3.client('timestream-write', region_name='us-east-1') -current_table = os.environ['TABLE_NAME'] -recent_table = os.environ['RECENT_TABLE_NAME'] +local_region = os.environ['AWS_REGION'] + +dynamo_client = boto3.client('dynamodb', region_name=local_region) +timestream_client = boto3.client('timestream-write', region_name=local_region) +tables = { + 'retail': { + 'recent': 'wow-token-price-recent', + 'current': 'wow-token-price', + 'timestream': 'wow-token-price-history' + }, + 'classic': { + 'recent': 'wow-token-classic-price-recent', + 'current': 'wow-token-classic-price', + 'timestream': 'wow-token-classic-price-history' + } +} # a lambda handler to handle eventbridge triggered vents def lambda_handler(event, context): + flavors = ['retail', 'classic'] regions = ['us', 'eu', 'kr', 'tw'] - live_prices = get_combined_live_price() - print(live_prices) - current_time_epoch = int(datetime.datetime.utcnow().timestamp()) + timestamp = int(datetime.datetime.utcnow().timestamp()) + for flavor in flavors: + flavor_handler(flavor, regions, timestamp) + + +def flavor_handler(flavor: str, regions: list, timestamp: int) -> None: + live_prices = get_combined_live_price(flavor) + print(f"Current {flavor} prices:\t{live_prices}") for region in regions: print(f'Updating {region.upper()}...') - update_token_price(region, current_time_epoch, live_prices[region]) + update_token_price(flavor, region, timestamp, live_prices[region]) -def update_token_price(region: str, current_time_epoch: int, live_price: int): - stored_price = get_stored_price(region) +def update_token_price(flavor: str, region: str, current_time_epoch: int, live_price: int): + stored_price = get_stored_price(flavor, region) + regional_item = get_regional_update_item(flavor, region) + print(f'Current live price {live_price}') print(f'Current stored price {stored_price}') + regional_price = int(regional_item['price']['S']) + print(f'Current regional price {regional_price}') if stored_price != live_price: - update_stored_token_price(region, live_price, current_time_epoch) - update_recent_token_price(region, live_price, current_time_epoch) - update_timestream_token_price(region, live_price, current_time_epoch) + # If the stored price is not the same as the live price, nor the regional price + # assume no other Lambda has updated it and update it + print(f"Stored price is differing from the live price, updating all databases") + update_stored_token_price(flavor, region, live_price, current_time_epoch) + update_recent_token_price(flavor, region, live_price, current_time_epoch) + update_regional_price(flavor, region, live_price, current_time_epoch) + update_timestream_token_price(flavor, region, live_price, current_time_epoch) + if (stored_price == live_price) and (regional_price != stored_price): + print(f"Stored price differs from regional price but not live price, updating regional databases") + update_regional_price(flavor, region, live_price, current_time_epoch) + update_timestream_token_price(flavor, region, live_price, current_time_epoch) else: - # price hasn't changed - print(f'Price has not changed for {region.upper()}') + print(f"Price hasn't changed for {flavor} {region.upper()}") # update the current price records in DynamoDB -def update_stored_token_price(region: str, price: int, current_time_epoch: int) -> None: +def update_stored_token_price(flavor: str, region: str, price: int, current_time_epoch: int) -> None: dynamo_client.update_item( - TableName=current_table, + TableName=tables[flavor]['current'], Key={ 'region': { 'S': region @@ -55,13 +84,61 @@ def update_stored_token_price(region: str, price: int, current_time_epoch: int) } # ReturnValues="UPDATED_NEW" ) - print(f'Updated {region.upper()} price to {price}') + print(f'Updated {flavor} {region.upper()} price to {price}') + + +def update_regional_price(flavor: str, region: str, price: int, current_time_epoch: int) -> None: + if flavor == 'retail': + key = region + else: + key = f"{flavor}-{region}" + dynamo_client.update_item( + TableName='wow-token-regional', + Key={ + 'region': { + 'S': key + } + }, + UpdateExpression='SET price = :p, current_time = :t', + ExpressionAttributeValues={ + ':p': { + 'S': str(price) + }, + ':t': { + 'S': str(current_time_epoch) + } + } + # ReturnValues="UPDATED_NEW" + ) + print(f'Updated regional {flavor} {region.upper()} price to {price}') + + +def create_regional_item(flavor: str, region: str): + print(f"Creating default regional item in {flavor} {region}") + if flavor == 'retail': + key = region + else: + key = f"{flavor}-{region}" + dynamo_client.put_item( + TableName='wow-token-regional', + Item={ + 'region': { + 'S': key + }, + 'price': { + 'S': str(1) + }, + 'timestamp': { + 'N': str(1) + } + } + ) # add a record to the recent token price table in DynamoDB -def update_recent_token_price(region: str, price: int, current_time_epoch: int) -> None: +def update_recent_token_price(flavor: str, region: str, price: int, current_time_epoch: int) -> None: dynamo_client.put_item( - TableName=recent_table, + TableName=tables[flavor]['recent'], Item={ 'region': { 'S': region @@ -78,16 +155,16 @@ def update_recent_token_price(region: str, price: int, current_time_epoch: int) } # ReturnValues="UPDATED_NEW" ) - print(f'Added {region.upper()} price {price} to {recent_table} table') + print(f'Added {region.upper()} price {price} to {tables[flavor]["recent"]} table') -def update_timestream_token_price(region: str, price: int, current_time_epoch: int) -> None: +def update_timestream_token_price(flavor: str, region: str, price: int, current_time_epoch: int) -> None: record_inserted = False while not record_inserted: try: print('Attempting to write to Timestream') timestream_client.write_records( - DatabaseName=os.environ['TIMESTREAM_DATABASE'], + DatabaseName=tables[flavor]['timestream'], TableName=f'{region}-price-history', Records=[ build_timestream_record(region, price, current_time_epoch), @@ -97,7 +174,7 @@ def update_timestream_token_price(region: str, price: int, current_time_epoch: i except Exception as e: print(f'Error writing to Timestream: {e}') time.sleep(2) - print(f'Updated {region.upper()} price to {price} in Timestream') + print(f'Updated {flavor} {region.upper()} price to {price} in Timestream') def build_timestream_record(region: str, price: int, current_time_epoch: int) -> dict: @@ -118,9 +195,9 @@ def build_timestream_record(region: str, price: int, current_time_epoch: int) -> # get the current stored token price from dynamodb -def get_stored_price(region: str) -> int: +def get_stored_price(flavor: str, region: str) -> int: response = dynamo_client.get_item( - TableName=current_table, + TableName=tables[flavor]['current'], Key={ 'region': { 'S': region @@ -130,18 +207,44 @@ def get_stored_price(region: str) -> int: return int(response['Item']['price']['S']) -def get_combined_live_price() -> dict: +def get_regional_update_item(flavor: str, region: str) -> dict: + if flavor == 'retail': + key = region + else: + key = f"{flavor}-{region}" + response = dynamo_client.get_item( + TableName='wow-token-regional', + Key={ + 'region': { + 'S': key + } + } + ) + if 'Item' in response: + return response['Item'] + else: + create_regional_item(flavor, region) + time.sleep(5) + return get_regional_update_item(flavor, region) + + +def get_combined_live_price(game_flavor: str) -> dict: + if game_flavor == 'retail': + namespace = 'dynamic' + else: + namespace = 'dynamic-classic' + return { - 'us': get_token_price_from_blizzard('us'), - 'eu': get_token_price_from_blizzard('eu'), - 'kr': get_token_price_from_blizzard('kr'), - 'tw': get_token_price_from_blizzard('tw') + 'us': get_token_price_from_blizzard('us', namespace), + 'eu': get_token_price_from_blizzard('eu', namespace), + 'kr': get_token_price_from_blizzard('kr', namespace), + 'tw': get_token_price_from_blizzard('tw', namespace) } -def get_token_price_from_blizzard(region: str): +def get_token_price_from_blizzard(region: str, namespace: str): api_endpoint = f'https://{region}.api.blizzard.com/data/wow/token/index' - params = {'namespace': f'{os.environ.get("BLIZZARD_NAMESPACE")}-{region}'} + params = {'namespace': f'{namespace}-{region}'} headers = {'Authorization': f'Bearer {get_oauth_token()}'} response = requests.get(api_endpoint, params=params, headers=headers) return int(response.json()['price'])