import os from datetime import datetime, timedelta import pytz 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 glom import glom from layercake.dateutils import fromisoformat, now, ttl from layercake.dynamodb import DynamoDBPersistenceLayer from layercake.funcs import pick from boto3clients import dynamodb_client from config import ENROLLMENT_TABLE logger = Logger(__name__) dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) tz = os.getenv('TZ', 'UTC') @event_source(data_class=EventBridgeEvent) @logger.inject_lambda_context def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool | None: new_image = event.detail['new_image'] expires_at = glom(new_image, 'cert.expires_at', default=None) if not expires_at: return None enrollment_id = new_image['id'] org_id = new_image['org_id'] expires_at: datetime = fromisoformat(expires_at).replace(tzinfo=pytz.timezone(tz)) # type: ignore # The reporting month is the month before the certificate expires month_start = (expires_at.replace(day=1) - timedelta(days=1)).replace(day=1) now_ = now() pk = f'CERT#REPORTING#ORG#{org_id}' sk = 'MONTH#{}'.format(expires_at.strftime('%Y-%m')) if now_ > expires_at: return None try: with dyn.transact_writer() as transact: transact.put( item={ 'id': pk, 'sk': 'MONTH#{}#SCHEDULE#SEND_REPORT_EMAIL'.format( month_start.strftime('%Y-%m') ), 'target_month': expires_at.strftime('%Y-%m'), 'ttl': ttl(start_dt=month_start), } ) transact.put( item={ 'id': pk, 'sk': f'{sk}#ENROLLMENT#{enrollment_id}', 'enrollment_id': new_image['id'], 'user': pick(('id', 'name'), new_image['user']), 'course': pick(('id', 'name'), new_image['course']), 'enrolled_at': new_image['created_at'], 'expires_at': expires_at, # type: ignore 'completed_at': new_image['completed_at'], 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', exc_cls=EnrollmentConflictError, ) except EnrollmentConflictError: return False return True class EnrollmentConflictError(Exception): ...