diff --git a/enrollments-events/app/events/emails/reminder_no_access_3_days.py b/enrollments-events/app/events/emails/reminder_no_access_3_days.py index cd041de..249b905 100644 --- a/enrollments-events/app/events/emails/reminder_no_access_3_days.py +++ b/enrollments-events/app/events/emails/reminder_no_access_3_days.py @@ -68,8 +68,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: }, } ) + logger.info('Email sent') except Exception as exc: logger.exception(exc) + enrollment_layer.put_item( item={ 'id': old_image['id'], diff --git a/enrollments-events/app/events/stopgap/patch_konviva.py b/enrollments-events/app/events/stopgap/patch_konviva.py new file mode 100644 index 0000000..27075a7 --- /dev/null +++ b/enrollments-events/app/events/stopgap/patch_konviva.py @@ -0,0 +1,87 @@ +import json +import sqlite3 + +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 now +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey +from sqlite_utils import Database + +from boto3clients import dynamodb_client +from config import ( + COURSE_TABLE, + ENROLLMENT_TABLE, + SQLITE_DATABASE, + SQLITE_TABLE, + USER_TABLE, +) + +sqlite3.register_converter('json', json.loads) + +logger = Logger(__name__) +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() + course_id = glom(new_image, 'course.id') + + data = enrollment_layer.collection.get_items( + KeyPair( + pk=glom(new_image, 'user.id'), + sk=SortKey('konviva', path_spec='konvivaId'), + rename_key='user_id', + table_name=USER_TABLE, + ) + + KeyPair( + pk=course_id, + sk=SortKey('0', path_spec='metadata__konviva_class_id'), + table_name=COURSE_TABLE, + rename_key='class_id', + ), + flatten_top=False, + ) + + if 'class_id' not in data: + data['class_id'] = _get_class_id(course_id) + + logger.info('IDs found', ids=data) + + return enrollment_layer.put_item( + item={ + 'id': new_image['id'], + 'sk': 'konviva', + 'user_id': data['user_id'], + 'class_id': data['class_id'], + 'created_at': now_, + }, + cond_expr='attribute_not_exists(sk)', + ) + + +class CourseNotFoundError(Exception): + def __init__(self, *args): + super().__init__('Course not found') + + +def _get_class_id(course_id: str) -> int: + with sqlite3.connect( + database=SQLITE_DATABASE, detect_types=sqlite3.PARSE_DECLTYPES + ) as conn: + db = Database(conn) + rows = db[SQLITE_TABLE].rows_where( + "json->>'$.metadata__betaeducacao_id' = ?", [course_id] + ) + + for row in rows: + return int(row['json']['metadata__konviva_id']) + + raise CourseNotFoundError diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index 06f660e..a24b2b7 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -62,6 +62,30 @@ Resources: new_image: sk: ["0"] + EventPatchKonvivaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.stopgap.patch_konviva.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBWritePolicy: + TableName: !Ref EnrollmentTable + - DynamoDBReadPolicy: + TableName: !Ref UserTable + - DynamoDBReadPolicy: + TableName: !Ref CourseTable + Events: + DynamoDBEvent: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref EnrollmentTable] + detail-type: [INSERT] + detail: + new_image: + sk: ["0"] + EventAllocateSlotsFunction: Type: AWS::Serverless::Function Properties: diff --git a/enrollments-events/tests/events/stopgap/test_patch_konviva.py b/enrollments-events/tests/events/stopgap/test_patch_konviva.py new file mode 100644 index 0000000..0dcf2cf --- /dev/null +++ b/enrollments-events/tests/events/stopgap/test_patch_konviva.py @@ -0,0 +1,27 @@ +from aws_lambda_powertools.utilities.typing import LambdaContext +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair + +import events.stopgap.patch_konviva as app + + +def test_patch_konviva( + dynamodb_seeds, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + event = { + 'detail': { + 'new_image': { + 'id': 'f321f0aa-66a3-4419-b41f-cb6b961e7e4f', + 'user': {'id': '5OxmMjL-ujoR5IMGegQz'}, + 'course': {'id': '123'}, + }, + } + } + assert app.lambda_handler(event, lambda_context) + + r = dynamodb_persistence_layer.collection.get_item( + KeyPair('f321f0aa-66a3-4419-b41f-cb6b961e7e4f', 'konviva') + ) + + assert 'user_id' in r diff --git a/enrollments-events/tests/seeds.jsonl b/enrollments-events/tests/seeds.jsonl index a44e31e..1b05a8a 100644 --- a/enrollments-events/tests/seeds.jsonl +++ b/enrollments-events/tests/seeds.jsonl @@ -5,4 +5,5 @@ {"id": {"S": "JeCybf6oiv6CF3PchhBqdG"}, "sk": {"S": "items"},"items": {"L": [{"M": {"id": {"S": "a955518e-ebcb-4441-b914-ddc9ecef84f0"},"name": {"S": "NR-11 Operador de Munck"},"quantity": {"N": "3"},"unit_price": {"N": "99"}}}, {"M": {"id": {"S": "123"},"name": {"S": "pytest"},"quantity": {"N": "1"},"unit_price": {"N": "99"}}},{"M": {"id": {"S": "23020"},"name": {"S": "Desconto 100%"},"quantity": {"N": "1"},"unit_price": {"N": "-297"}}}]},"updated_at": {"S": "2025-07-16T15:54:27.154404-03:00"}} {"id": {"S": "JeCybf6oiv6CF3PchhBqdG"},"sk": {"S": "generated_items"},"create_date": {"S": "2025-07-16T15:54:30.160729-03:00"},"scope": {"S": "MULTI_USER"},"status": {"S": "SUCCESS"},"update_date": {"S": "2025-07-16T15:54:33.674670-03:00"}} {"id": {"S": "a955518e-ebcb-4441-b914-ddc9ecef84f0"},"sk": {"S": "0"},"access_period": {"N": "360"},"cert": {"M": {"exp_interval": {"N": "360"}}},"created_at": {"S": "2025-07-14T15:09:18.559528-03:00"},"metadata__konviva_class_id": {"N": "281"},"name": {"S": "NR-11 Operador de Munck"},"tenant_id": {"S": "*"}} -{"id": {"S": "123"},"sk": {"S": "0"},"access_period": {"N": "360"},"cert": {"M": {"exp_interval": {"N": "360"}}},"created_at": {"S": "2025-07-14T15:09:18.559528-03:00"},"metadata__konviva_class_id": {"N": "281"},"name": {"S": "pytest"},"tenant_id": {"S": "*"}} \ No newline at end of file +{"id": {"S": "123"},"sk": {"S": "0"},"access_period": {"N": "360"},"cert": {"M": {"exp_interval": {"N": "360"}}},"created_at": {"S": "2025-07-14T15:09:18.559528-03:00"},"metadata__konviva_class_id": {"N": "281"},"name": {"S": "pytest"},"tenant_id": {"S": "*"}} +{"id": {"S": "5OxmMjL-ujoR5IMGegQz"},"sk": {"S": "konviva"},"created_at": {"S": "2025-07-11T13:52:35.521154-03:00"},"konvivaId": {"N": "26943"}} \ No newline at end of file