import os from datetime import 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 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'] enrollment_id = new_image['id'] org_id = new_image['org_id'] expires_at = fromisoformat(new_image['cert_expires_at']).replace( # type: ignore tzinfo=pytz.timezone(tz) ) target_month = expires_at.strftime('%Y-%m') now_ = now(tz) pk = f'CERT_REPORTING#ORG#{org_id}' try: if now_ > expires_at: raise InvalidDateError('Invalid date') # The reporting month is the month before the certificate expires report_month = (expires_at.replace(day=1) - timedelta(days=1)).replace(day=1) report_sk = report_month.strftime('%Y-%m') with dyn.transact_writer() as transact: transact.put( item={ 'id': pk, 'sk': f'MONTH#{report_sk}', 'status': 'PENDING', 'target_month': target_month, 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', exc_cls=ReportExistsError, ) transact.put( item={ 'id': pk, 'sk': f'MONTH#{report_sk}#SCHEDULE#SEND_REPORT_EMAIL', 'target_month': target_month, 'ttl': ttl(start_dt=report_month), 'created_at': now_, }, ) except Exception as exc: logger.info(exc) try: dyn.put_item( item={ 'id': pk, 'sk': f'MONTH#{target_month}#ENROLLMENT#{enrollment_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, 'completed_at': new_image['completed_at'], 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', ) except Exception: return False else: return True class InvalidDateError(Exception): ... class ReportExistsError(Exception): def __init__(self, *args: object) -> None: super().__init__('Report already exists')