from datetime import datetime, time, timedelta 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 now, ttl from layercake.dynamodb import ( DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey, ) from layercake.funcs import pick from boto3clients import dynamodb_client from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE from utils import get_billing_period logger = Logger(__name__) order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) @event_source(data_class=EventBridgeEvent) @logger.inject_lambda_context def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] now_ = now() enrollment_id = new_image['id'] org_id = new_image['org_id'] data = enrollment_layer.collection.get_items( TransactKey(enrollment_id) + SortKey('0') + SortKey('author') ) if not data: logger.debug('Enrollment not found') return False start_date, end_date = get_billing_period(new_image['billing_day']) pk = 'BILLING#ORG#{org_id}'.format(org_id=org_id) sk = 'START#{start}#END#{end}'.format( start=start_date.isoformat(), end=end_date.isoformat(), ) try: with order_layer.transact_writer() as transact: transact.put( item={ 'id': pk, 'sk': sk, 'status': 'PENDING', 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', exc_cls=ExistingBillingConflictError, ) transact.put( item={ 'id': pk, 'sk': f'{sk}#SCHEDULE#AUTO_CLOSE', 'ttl': ttl( start_dt=datetime.combine(end_date, time()) + timedelta(days=1) ), 'created_at': now_, } ) except ExistingBillingConflictError: pass try: with order_layer.transact_writer() as transact: author = data['author'] course_id = data['course']['id'] course = course_layer.collection.get_items( KeyPair( pk=course_id, sk=SortKey('0', path_spec='metadata__unit_price'), rename_key='unit_price', ) + KeyPair( pk=f'CUSTOM_PRICING#ORG#{org_id}', sk=SortKey(f'COURSE#{course_id}', path_spec='unit_price'), rename_key='unit_price', ), flatten_top=False, ) transact.condition( key=KeyPair(pk, sk), cond_expr='attribute_exists(sk)', exc_cls=BillingNotFoundError, ) transact.put( item={ 'id': pk, 'sk': f'{sk}#ENROLLMENT#{enrollment_id}', 'user': pick(('id', 'name'), data['user']), 'course': pick(('id', 'name'), data['course']), 'unit_price': course['unit_price'], 'author': { 'id': author['user_id'], 'name': author['name'], }, # Post-migration: uncomment the following line # 'enrolled_at': data['created_at'], 'enrolled_at': data['create_date'], 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', ) except Exception: return False else: return True class ExistingBillingConflictError(Exception): ... class BillingNotFoundError(Exception): ...