from datetime import datetime from aws_lambda_powertools import Logger from aws_lambda_powertools.utilities.data_classes import ( EventBridgeEvent, event_source, ) from aws_lambda_powertools.utilities.typing import LambdaContext from layercake.dateutils import fromisoformat, now from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey from layercake.funcs import pick from boto3clients import dynamodb_client from config import ENROLLMENT_TABLE, ORDER_TABLE from utils import get_billing_period logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) @event_source(data_class=EventBridgeEvent) @logger.inject_lambda_context def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: now_ = now() new_image = event.detail['new_image'] enrollment_id = new_image['id'] subscription = enrollment_layer.collection.get_items( TransactKey(enrollment_id) + SortKey('METADATA#SUBSCRIPTION_COVERED') + SortKey('CANCELED', path_spec='canceled_by', rename_key='canceled_by') ) created_at: datetime = fromisoformat(new_image['created_at']) # type: ignore start_period, end_period = get_billing_period( billing_day=int(subscription['billing_day']), date_=created_at, ) pk = 'BILLING#ORG#{org_id}'.format(org_id=subscription['org_id']) sk = 'START#{start}#END#{end}'.format( start=start_period.isoformat(), end=end_period.isoformat(), ) if now_.date() > end_period: logger.debug('Enrollment outside the billing period') return False try: canceled_by = subscription.get('canceled_by') # Retrieve canceled enrollment data old_enrollment = order_layer.collection.get_item( KeyPair( pk=pk, sk=f'{sk}#ENROLLMENT#{enrollment_id}', ), exc_cls=EnrollmentNotFoundError, ) order_layer.put_item( item={ 'id': pk, 'sk': f'{sk}#ENROLLMENT#{enrollment_id}#CANCELED', 'unit_price': old_enrollment['unit_price'] * -1, 'created_at': now_, } | pick(('user', 'course', 'enrolled_at'), old_enrollment) # Add `created_by` if present | ({'author': canceled_by} if canceled_by else {}), # Post-migration: uncomment the following line # | ({'created_by': canceled_by} if canceled_by else {}), cond_expr='attribute_not_exists(sk)', ) except Exception as exc: logger.exception( exc, keypair={'pk': pk, 'sk': sk}, ) return False else: return True class EnrollmentNotFoundError(Exception): ...