diff --git a/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py b/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py index 2722142..b43d828 100644 --- a/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py +++ b/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py @@ -54,7 +54,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: sesv2_client=sesv2_client, event={ 'id': old_image['id'], - 'sk': 'SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS', + 'sk': 'SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS', }, dynamodb_persistence_layer=enrollment_layer, ) diff --git a/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py b/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py index 18ca36f..63acca9 100644 --- a/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py +++ b/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py @@ -55,7 +55,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: sesv2_client=sesv2_client, event={ 'id': old_image['id'], - 'sk': 'SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS', + 'sk': 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS', }, dynamodb_persistence_layer=enrollment_layer, ) diff --git a/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py b/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py index a620b6b..d96dfdd 100644 --- a/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py +++ b/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py @@ -17,6 +17,7 @@ from .email_ import send_email logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) + SUBJECT = 'Seu curso de {course} está esperando por você na EDUSEG®' MESSAGE = """ Oi {first_name}, tudo bem?

diff --git a/enrollments-events/app/events/issue_cert.py b/enrollments-events/app/events/issue_cert.py index e48c7d3..817507d 100644 --- a/enrollments-events/app/events/issue_cert.py +++ b/enrollments-events/app/events/issue_cert.py @@ -4,7 +4,7 @@ from aws_lambda_powertools.utilities.data_classes import ( event_source, ) from aws_lambda_powertools.utilities.typing import LambdaContext -from layercake.dynamodb import DynamoDBPersistenceLayer +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey from boto3clients import dynamodb_client from config import ( @@ -19,5 +19,10 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) @logger.inject_lambda_context def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] + enrollment = enrollment_layer.collection.get_items( + TransactKey(pk=new_image['id']) + + SortKey('METADATA#DEDUPLICATION_WINDOW', rename_key='DEDUPLICATION_WINDOW') + + SortKey('METADATA#COURSE', rename_key='COURSE'), + ) return True diff --git a/enrollments-events/app/events/reenroll_if_failed.py b/enrollments-events/app/events/reenroll_if_failed.py index c3bb895..4fdb40a 100644 --- a/enrollments-events/app/events/reenroll_if_failed.py +++ b/enrollments-events/app/events/reenroll_if_failed.py @@ -22,11 +22,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: subscription = enrollment_layer.collection.get_items( TransactKey(new_image['id']) + SortKey('METADATA#SUBSCRIPTION_COVERED') - + SortKey('author') - + SortKey('tenant') - # Post-migration: uncommet the following lines - # + SortKey('CREATED_BY') - # + SortKey('ORG') + + SortKey('author', rename_key='CREATED_BY') + + SortKey('tenant', rename_key='ORG') + + SortKey('CREATED_BY') + + SortKey('ORG') ) with enrollment_layer.transact_writer() as transact_writer: diff --git a/enrollments-events/app/events/schedule_reminders.py b/enrollments-events/app/events/schedule_reminders.py index a3bad8c..0f61a44 100644 --- a/enrollments-events/app/events/schedule_reminders.py +++ b/enrollments-events/app/events/schedule_reminders.py @@ -1,5 +1,3 @@ -from datetime import timedelta - from aws_lambda_powertools import Logger from aws_lambda_powertools.utilities.data_classes import ( EventBridgeEvent, @@ -15,7 +13,7 @@ from config import ( ) logger = Logger(__name__) -enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) +dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) @event_source(data_class=EventBridgeEvent) @@ -27,7 +25,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: course = new_image['course'] now_ = now() - with enrollment_layer.transact_writer() as transact: + with dyn.transact_writer() as transact: transact.put( item={ 'id': enrollment_id, @@ -48,22 +46,19 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: item={ 'id': enrollment_id, 'sk': 'SCHEDULE#SET_AS_EXPIRED', - 'name': user.name, - 'email': user.email, - 'course': course.name, 'created_at': now_, - 'ttl': ttl(start_dt=now_ + timedelta(days=course.access_period)), + 'ttl': ttl(start_dt=now_, days=course.access_period), }, ) transact.put( item={ 'id': enrollment_id, - 'sk': 'REMINDER_ACCESS_PERIOD_BEFORE_15_DAYS', + 'sk': 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS', 'name': user.name, 'email': user.email, 'course': course.name, 'created_at': now_, - 'ttl': ttl(start_dt=now_ + timedelta(days=course.access_period - 15)), + 'ttl': ttl(start_dt=now_, days=course.access_period - 30), }, ) diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index e8ca516..5a0123e 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -310,10 +310,10 @@ Resources: # Post-migration: remove the following line - schedules#expiration - EventScheduleRemindersFunction: + EventSetAsExpiredFunction: Type: AWS::Serverless::Function Properties: - Handler: events.schedule_reminders.lambda_handler + Handler: events.set_as_expired.lambda_handler LoggingConfig: LogGroup: !Ref EventLog Policies: @@ -325,11 +325,31 @@ Resources: Properties: Pattern: resources: [!Ref EnrollmentTable] - detail-type: [INSERT] + detail-type: [EXPIRE] detail: - new_image: - sk: ["0"] - status: [PENDING] + keys: + sk: [SCHEDULE#SET_AS_EXPIRED] + + # EventScheduleRemindersFunction: + # Type: AWS::Serverless::Function + # Properties: + # Handler: events.schedule_reminders.lambda_handler + # LoggingConfig: + # LogGroup: !Ref EventLog + # Policies: + # - DynamoDBCrudPolicy: + # TableName: !Ref EnrollmentTable + # Events: + # DynamoDBEvent: + # Type: EventBridgeRule + # Properties: + # Pattern: + # resources: [!Ref EnrollmentTable] + # detail-type: [INSERT] + # detail: + # new_image: + # sk: ["0"] + # status: [PENDING] EventIssueCertFunction: Type: AWS::Serverless::Function diff --git a/enrollments-events/tests/conftest.py b/enrollments-events/tests/conftest.py index b9c7d7e..336ca0b 100644 --- a/enrollments-events/tests/conftest.py +++ b/enrollments-events/tests/conftest.py @@ -67,7 +67,14 @@ def dynamodb_persistence_layer(dynamodb_client): @pytest.fixture() -def dynamodb_seeds(dynamodb_client): - with jsonlines.open('tests/seeds.jsonl') as lines: - for line in lines: - dynamodb_client.put_item(TableName=PYTEST_TABLE_NAME, Item=line) +def seeds(dynamodb_client): + from layercake.dynamodb import serialize + + with open('tests/seeds.jsonl', 'rb') as fp: + reader = jsonlines.Reader(fp) + + for line in reader.iter(type=dict, skip_invalid=True): + dynamodb_client.put_item( + TableName=PYTEST_TABLE_NAME, + Item=serialize(line), + ) diff --git a/enrollments-events/tests/events/emails/test_reminder_access_period_before_30_days.py b/enrollments-events/tests/events/emails/test_reminder_access_period_before_30_days.py index 8a83387..cb97d45 100644 --- a/enrollments-events/tests/events/emails/test_reminder_access_period_before_30_days.py +++ b/enrollments-events/tests/events/emails/test_reminder_access_period_before_30_days.py @@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.typing import LambdaContext def test_reminder_access_period_before_30_days( - dynamodb_seeds, + seeds, lambda_context: LambdaContext, ): event = { diff --git a/enrollments-events/tests/events/emails/test_reminder_cert_expiration_before_30_days.py b/enrollments-events/tests/events/emails/test_reminder_cert_expiration_before_30_days.py index 0f2c986..f116c53 100644 --- a/enrollments-events/tests/events/emails/test_reminder_cert_expiration_before_30_days.py +++ b/enrollments-events/tests/events/emails/test_reminder_cert_expiration_before_30_days.py @@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.typing import LambdaContext def test_reminder_cert_expiration_before_30_days( - dynamodb_seeds, + seeds, lambda_context: LambdaContext, ): event = { diff --git a/enrollments-events/tests/events/emails/test_reminder_no_access_after_3_days.py b/enrollments-events/tests/events/emails/test_reminder_no_access_after_3_days.py index 64a6188..9b04f8f 100644 --- a/enrollments-events/tests/events/emails/test_reminder_no_access_after_3_days.py +++ b/enrollments-events/tests/events/emails/test_reminder_no_access_after_3_days.py @@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.typing import LambdaContext def test_reminder_no_access_after_3_days( - dynamodb_seeds, + seeds, lambda_context: LambdaContext, ): event = { diff --git a/enrollments-events/tests/events/emails/test_reminder_no_activity_after_7_days.py b/enrollments-events/tests/events/emails/test_reminder_no_activity_after_7_days.py index 98722ea..b4e7ac9 100644 --- a/enrollments-events/tests/events/emails/test_reminder_no_activity_after_7_days.py +++ b/enrollments-events/tests/events/emails/test_reminder_no_activity_after_7_days.py @@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.typing import LambdaContext def test_reminder_no_activity_after_7_days( - dynamodb_seeds, + seeds, lambda_context: LambdaContext, ): event = { diff --git a/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py b/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py index e0f7518..58b30a1 100644 --- a/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py +++ b/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py @@ -8,8 +8,7 @@ from layercake.dynamodb import ( def test_enroll( - dynamodb_seeds, - dynamodb_client, + seeds, dynamodb_persistence_layer: DynamoDBPersistenceLayer, lambda_context: LambdaContext, ): diff --git a/enrollments-events/tests/events/stopgap/test_patch_konviva.py b/enrollments-events/tests/events/stopgap/test_patch_konviva.py index 0dcf2cf..1c07a0b 100644 --- a/enrollments-events/tests/events/stopgap/test_patch_konviva.py +++ b/enrollments-events/tests/events/stopgap/test_patch_konviva.py @@ -5,7 +5,7 @@ import events.stopgap.patch_konviva as app def test_patch_konviva( - dynamodb_seeds, + seeds, dynamodb_persistence_layer: DynamoDBPersistenceLayer, lambda_context: LambdaContext, ): @@ -18,7 +18,7 @@ def test_patch_konviva( }, } } - assert app.lambda_handler(event, lambda_context) + assert app.lambda_handler(event, lambda_context) # type: ignore r = dynamodb_persistence_layer.collection.get_item( KeyPair('f321f0aa-66a3-4419-b41f-cb6b961e7e4f', 'konviva') diff --git a/enrollments-events/tests/events/test_allocate_slots.py b/enrollments-events/tests/events/test_allocate_slots.py index 12df2bb..2e6e0df 100644 --- a/enrollments-events/tests/events/test_allocate_slots.py +++ b/enrollments-events/tests/events/test_allocate_slots.py @@ -4,8 +4,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey def test_allocate_slots( - dynamodb_seeds, - dynamodb_client, + seeds, dynamodb_persistence_layer: DynamoDBPersistenceLayer, lambda_context: LambdaContext, ): diff --git a/enrollments-events/tests/events/test_enroll.py b/enrollments-events/tests/events/test_enroll.py index 3511b66..59e32f2 100644 --- a/enrollments-events/tests/events/test_enroll.py +++ b/enrollments-events/tests/events/test_enroll.py @@ -4,8 +4,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer def test_enroll( - dynamodb_seeds, - dynamodb_client, + seeds, dynamodb_persistence_layer: DynamoDBPersistenceLayer, lambda_context: LambdaContext, ): diff --git a/enrollments-events/tests/seeds.jsonl b/enrollments-events/tests/seeds.jsonl index 4b13351..f257de0 100644 --- a/enrollments-events/tests/seeds.jsonl +++ b/enrollments-events/tests/seeds.jsonl @@ -1,13 +1,23 @@ -{"id": {"S": "47ZxxcVBjvhDS5TE98tpfQ"}, "sk": {"S": "0"}, "course": {"M": {"id": {"S": "42"}, "name": {"S": "NR-35 Segurança nos Trabalhos em Altura (Teórico)"},"time_in_days": {"N": "720"}}},"create_date": {"S": "2025-04-10T11:58:33.303347-03:00"},"konviva:id": {"N": "238662"},"progress": {"N": "16.67"},"score": {"NULL": true},"status": {"S": "IN_PROGRESS"}, "update_date": {"S": "2025-04-10T15:44:03.023054-03:00"}, "user": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "cpf": {"S": "07879819908"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "Sérgio Rafael Siqueira"}}}} -{"id": {"S": "47ZxxcVBjvhDS5TE98tpfQ"}, "sk": {"S": "konviva"}, "create_date": {"S": "2025-04-10T11:58:35.035729-03:00"}, "konviva_id": {"N": "238662"}} -{"id": {"S": "47ZxxcVBjvhDS5TE98tpfQ"}, "sk": {"S": "tenant"}, "create_date": {"S": "2025-04-10T11:58:33.303347-03:00"}, "name": {"S": "Beta Educação"},"org_id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}} -{"id": {"S": "JeCybf6oiv6CF3PchhBqdG"}, "sk": {"S": "0"}, "assignee": {"M": {"name": {"S": "Rodrigo Silva"}}},"cnpj": {"S": "54183587001708"},"create_date": {"S": "2025-07-16T15:54:24.304566-03:00"},"due_date": {"S": "2025-07-17T15:50:08.139000-03:00"},"email": {"S": "silva.rodrigo@manserv.com.br"},"name": {"S": "MANSERV MONTAGEM E MANUTENCAO S/A"},"payment_date": {"NULL": true},"payment_method": {"S": "MANUAL"},"status": {"S": "PAID"},"tenant_id": {"S": "PWQyjGZeVcoD5zc5eTkvpr"},"total": {"N": "0"},"updated_at": {"S": "2025-07-16T15:54:27.074086-03:00"},"update_date": {"S": "2025-07-16T15:54:26.697884-03:00"}} -{"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": "6a403773-aeac-4e6a-ac39-dc958e4be52a"},"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": "Reciclagem em NR-11 - Operador de Empilhadeira"},"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": "*"}} -{"id": {"S": "5OxmMjL-ujoR5IMGegQz"},"sk": {"S": "konviva"},"created_at": {"S": "2025-07-11T13:52:35.521154-03:00"},"konvivaId": {"N": "26943"}} -{"id": {"S": "cpYSbBcie2NDbZhDKCxCih"}, "sk": {"S": "0"},"cpf": {"S": "02713421535"},"create_date": {"S": "2025-07-21T16:19:43.297712-03:00"},"due_date": {"S": "2025-07-22T16:13:41.056000-03:00"},"email": {"S": "sergio@somosbeta.com.br"},"name": {"S": "Sérgio Rafael Siqueira"},"payment_date": {"S": "2025-07-21T16:21:47.161889-03:00"},"payment_method": {"S": "PIX"},"phone_number": {"S": "+5574998189595"},"status": {"S": "PAID"},"total": {"N": "99"},"update_date": {"S": "2025-07-21T16:21:47.161889-03:00"}, "user_id": {"S": "5OxmMjL-ujoR5IMGegQz"}} -{"id": {"S": "cpYSbBcie2NDbZhDKCxCih"}, "sk": {"S": "items"},"items": {"L": [{"M": {"id": {"S": "6a403773-aeac-4e6a-ac39-dc958e4be52a"},"name": {"S": "Reciclagem em NR-11 - Operador de Empilhadeira"},"quantity": {"N": "1"},"unit_price": {"N": "99"}}}]}} -{"id": {"S": "cpYSbBcie2NDbZhDKCxCih"}, "sk": {"S": "generated_items"},"create_date": {"S": "2025-07-21T16:21:50.143551-03:00"},"scope": {"S": "SINGLE_USER"},"status": {"S": "SUCCESS"},"update_date": {"S": "2025-07-21T16:21:53.994941-03:00"}} \ No newline at end of file +{"id": "47ZxxcVBjvhDS5TE98tpfQ", "sk": "0", "course": {"id": "42", "name": "NR-35 Seguran\u00e7a nos Trabalhos em Altura (Te\u00f3rico)", "time_in_days": 720}, "create_date": "2025-04-10T11:58:33.303347-03:00", "konviva:id": 238662, "progress": "16.67", "score": null, "status": "IN_PROGRESS", "update_date": "2025-04-10T15:44:03.023054-03:00", "user": {"id": "5OxmMjL-ujoR5IMGegQz", "cpf": "07879819908", "email": "sergio@somosbeta.com.br", "name": "S\u00e9rgio Rafael Siqueira"}} +{"id": "47ZxxcVBjvhDS5TE98tpfQ", "sk": "konviva", "create_date": "2025-04-10T11:58:35.035729-03:00", "konviva_id": "238662"} +{"id": "47ZxxcVBjvhDS5TE98tpfQ", "sk": "tenant", "create_date": "2025-04-10T11:58:33.303347-03:00", "name": "Beta Educa\u00e7\u00e3o", "org_id": "cJtK9SsnJhKPyxESe7g3DG"} +{"id": "JeCybf6oiv6CF3PchhBqdG", "sk": "0", "assignee": {"name": "Rodrigo Silva"}, "cnpj": "54183587001708", "create_date": "2025-07-16T15:54:24.304566-03:00", "due_date": "2025-07-17T15:50:08.139000-03:00", "email": "silva.rodrigo@manserv.com.br", "name": "MANSERV MONTAGEM E MANUTENCAO S/A", "payment_date": null, "payment_method": "MANUAL", "status": "PAID", "tenant_id": "PWQyjGZeVcoD5zc5eTkvpr", "total": "0", "updated_at": "2025-07-16T15:54:27.074086-03:00", "update_date": "2025-07-16T15:54:26.697884-03:00"} +{"id": "JeCybf6oiv6CF3PchhBqdG", "sk": "items", "items": [{"id": "a955518e-ebcb-4441-b914-ddc9ecef84f0", "name": "NR-11 Operador de Munck", "quantity": "3", "unit_price": 99}, {"id": "123", "name": "pytest", "quantity": 1, "unit_price": 99}, {"id": "23020", "name": "Desconto 100%", "quantity": 1, "unit_price": -297}], "updated_at": "2025-07-16T15:54:27.154404-03:00"} +{"id": "JeCybf6oiv6CF3PchhBqdG", "sk": "generated_items", "create_date": "2025-07-16T15:54:30.160729-03:00", "scope": "MULTI_USER", "status": "SUCCESS", "update_date": "2025-07-16T15:54:33.674670-03:00"} + +// Order +{"id": "cpYSbBcie2NDbZhDKCxCih", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br", "cpf": "07879819908", "user_id": "5OxmMjL-ujoR5IMGegQz"} +{"id": "cpYSbBcie2NDbZhDKCxCih", "sk": "items", "items": [{"id": "6a403773-aeac-4e6a-ac39-dc958e4be52a", "name": "Reciclagem em NR-11 - Operador de Empilhadeira", "quantity": 1, "unit_price": 99}]} +{"id": "cpYSbBcie2NDbZhDKCxCih", "sk": "generated_items"} + +// Course +{"id": "123", "sk": "0", "access_period": 360, "cert": {"exp_interval": 360}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "pytest", "tenant_id": "*"} +{"id": "a955518e-ebcb-4441-b914-ddc9ecef84f0", "sk": "0", "access_period": "360", "cert": {"exp_interval": 360}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "NR-11 Operador de Munck", "tenant_id": "*"} +{"id": "6a403773-aeac-4e6a-ac39-dc958e4be52a", "sk": "0", "access_period": "360", "cert": {"exp_interval": 360}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "Reciclagem em NR-11 - Operador de Empilhadeira", "tenant_id": "*"} + +// User data +{"id": "5OxmMjL-ujoR5IMGegQz", "sk": "konviva", "konvivaId": 26943} + +// Enrollment +{"id": "6437a282-6fe8-4e4d-9eb0-da1007238007", "sk": "0", "status": "IN_PROGRESS", "progress": 10} +{"id": "845fe390-e3c3-4514-97f8-c42de0566cf0", "sk": "0", "status": "COMPLETED", "progress": 100} \ No newline at end of file