from dataclasses import asdict, dataclass from uuid import uuid4 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 from layercake.dynamodb import ( DynamoDBPersistenceLayer, KeyChain, KeyPair, SortKey, TransactKey, ) from boto3clients import dynamodb_client from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE logger = Logger(__name__) order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_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() order_id = new_image['id'] order = order_layer.collection.get_items( TransactKey(order_id) + SortKey('0') + SortKey('items', path_spec='items'), ) # Post-migration (orders): rename `tenant_id` to `org_id` org_id = order['tenant_id'] items = { item['id']: int(item['quantity']) for item in order['items'] # Ignore items with non-positive unit price; # negative values are treated as discounts if item['unit_price'] > 0 } courses = _get_courses(set(items.keys())) slots = tuple(course for course in courses for _ in range(items.get(course.id, 0))) logger.info('Course slots generated', slots=slots) with enrollment_layer.transact_writer() as transact: for slot in slots: transact.put( item={ 'id': f'vacancies#{org_id}', 'sk': f'{order_id}#{uuid4()}', # Post-migration (orders): uncomment the follow lines # 'id': f'SLOT#ORG#{org_id}', # 'sk': f'ORDER#{order_id}#ENROLLMENT#{uuid4()}', 'course': asdict(slot), 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', ) return order_layer.update_item( key=KeyPair(new_image['id'], new_image['sk']), update_expr='SET #status = :status, updated_at = :now', expr_attr_names={ '#status': 'status', }, expr_attr_values={ ':status': 'SUCCESS', ':now': now_, }, cond_expr='attribute_exists(sk)', ) @dataclass(frozen=True) class Course: id: str name: str access_period: int def _get_courses(ids: set) -> tuple[Course, ...]: pairs = tuple(KeyPair(idx, '0') for idx in ids) r = course_layer.collection.get_items( KeyChain(pairs), flatten_top=False, ) courses = tuple( Course( id=idx, name=obj['name'], access_period=obj['access_period'], ) for idx, obj in r.items() ) return courses