This commit is contained in:
2025-10-14 23:38:39 -03:00
parent a7e5a0a528
commit 54c92b3996
11 changed files with 38 additions and 18 deletions

View File

@@ -2,7 +2,7 @@ from abc import ABC
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from enum import Enum from enum import Enum
from typing import NotRequired, Self, TypedDict from typing import NotRequired, TypedDict
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair

View File

@@ -9,11 +9,13 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ( from config import (
COURSE_TABLE,
ENROLLMENT_TABLE, ENROLLMENT_TABLE,
) )
logger = Logger(__name__) logger = Logger(__name__)
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
@event_source(data_class=EventBridgeEvent) @event_source(data_class=EventBridgeEvent)
@@ -28,7 +30,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
# Post-migration: after removing the stopgap file `patch_course_metadata.py`, # Post-migration: after removing the stopgap file `patch_course_metadata.py`,
# use `access_expires_at` from `new_image` # use `access_expires_at` from `new_image`
access_period = int( access_period = int(
dyn.collection.get_item( course_layer.collection.get_item(
KeyPair( KeyPair(
pk=new_image['course']['id'], pk=new_image['course']['id'],
sk=SortKey('0', path_spec='access_period'), sk=SortKey('0', path_spec='access_period'),
@@ -36,7 +38,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
) )
) )
with dyn.transact_writer() as transact: with enrollment_layer.transact_writer() as transact:
transact.put( transact.put(
item={ item={
'id': enrollment_id, 'id': enrollment_id,

View File

@@ -26,7 +26,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
terms = user_layer.get_item( terms = user_layer.get_item(
# Post-migration (users): uncomment the following line # Post-migration (users): uncomment the following line
# KeyPair(new_image['org_id'], 'METADATA#BILLING_TERMS'), # KeyPair(new_image['org_id'], 'METADATA#BILLING_TERMS'),
KeyPair(new_image['tenant_id'], 'metadata#billing_policy'), KeyPair(new_image['org_id'], 'metadata#billing_policy'),
) )
if not terms: if not terms:

View File

@@ -48,7 +48,7 @@ class Enrollment(BaseModel):
return super().model_dump( return super().model_dump(
exclude={ exclude={
'user': {'email_verified'}, 'user': {'email_verified'},
'course': {'cert', 'access_period'}, 'course': {'cert'},
}, },
*args, *args,
**kwargs, **kwargs,

View File

@@ -252,6 +252,8 @@ Resources:
Policies: Policies:
- DynamoDBCrudPolicy: - DynamoDBCrudPolicy:
TableName: !Ref EnrollmentTable TableName: !Ref EnrollmentTable
- DynamoDBReadPolicy:
TableName: !Ref CourseTable
Events: Events:
DynamoDBEvent: DynamoDBEvent:
Type: EventBridgeRule Type: EventBridgeRule

View File

@@ -1,6 +1,7 @@
import os import os
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
KONVIVA_API_URL: str = os.getenv('KONVIVA_API_URL') # type: ignore KONVIVA_API_URL: str = os.getenv('KONVIVA_API_URL') # type: ignore

View File

@@ -10,6 +10,12 @@ from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey
from layercake.strutils import md5_hash from layercake.strutils import md5_hash
from boto3clients import dynamodb_client
from config import COURSE_TABLE
# @TODO Find a better way
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
def update_progress( def update_progress(
id: str, id: str,
@@ -106,11 +112,6 @@ def set_score(
enrollment = dynamodb_persistence_layer.collection.get_items( enrollment = dynamodb_persistence_layer.collection.get_items(
TransactKey(id) TransactKey(id)
+ SortKey('0') + SortKey('0')
+ SortKey(
sk='METADATA#COURSE',
# Prevent conflicts with `course`
rename_key='metadata__course',
)
+ SortKey( + SortKey(
sk='METADATA#DEDUPLICATION_WINDOW', sk='METADATA#DEDUPLICATION_WINDOW',
path_spec='offset_days', path_spec='offset_days',
@@ -119,6 +120,14 @@ def set_score(
) )
user_id = enrollment['user']['id'] user_id = enrollment['user']['id']
course_id = glom(enrollment, 'course.id') course_id = glom(enrollment, 'course.id')
exp_interval = course_layer.collection.get_item(
KeyPair(
pk=course_id,
sk=SortKey('0', path_spec='cert.exp_interval'),
),
raise_on_error=False,
default=0,
)
try: try:
if score >= 70: if score >= 70:
@@ -129,9 +138,7 @@ def set_score(
progress=progress, progress=progress,
user_id=user_id, user_id=user_id,
course_id=course_id, course_id=course_id,
cert_exp_interval=int( cert_exp_interval=int(exp_interval),
glom(enrollment, 'metadata__course.cert.exp_interval', default=0)
),
dedup_window_offset_days=int(enrollment['dedup_window_offset_days']), dedup_window_offset_days=int(enrollment['dedup_window_offset_days']),
dynamodb_persistence_layer=dynamodb_persistence_layer, dynamodb_persistence_layer=dynamodb_persistence_layer,
) )

View File

@@ -8,6 +8,9 @@ Parameters:
EnrollmentTable: EnrollmentTable:
Type: String Type: String
Default: betaeducacao-prod-enrollments Default: betaeducacao-prod-enrollments
CourseTable:
Type: String
Default: saladeaula_courses
Globals: Globals:
Function: Function:
@@ -27,6 +30,7 @@ Globals:
POWERTOOLS_LOGGER_LOG_EVENT: true POWERTOOLS_LOGGER_LOG_EVENT: true
USER_TABLE: !Ref UserTable USER_TABLE: !Ref UserTable
ENROLLMENT_TABLE: !Ref EnrollmentTable ENROLLMENT_TABLE: !Ref EnrollmentTable
COURSE_TABLE: !Ref CourseTable
KONVIVA_API_URL: https://lms.saladeaula.digital KONVIVA_API_URL: https://lms.saladeaula.digital
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}" KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}"
@@ -58,6 +62,8 @@ Resources:
Policies: Policies:
- DynamoDBCrudPolicy: - DynamoDBCrudPolicy:
TableName: !Ref EnrollmentTable TableName: !Ref EnrollmentTable
- DynamoDBReadPolicy:
TableName: !Ref CourseTable
Events: Events:
Post: Post:
Type: HttpApi Type: HttpApi

View File

@@ -18,6 +18,7 @@ def pytest_configure():
os.environ['DYNAMODB_PARTITION_KEY'] = PK os.environ['DYNAMODB_PARTITION_KEY'] = PK
os.environ['DYNAMODB_SORT_KEY'] = SK os.environ['DYNAMODB_SORT_KEY'] = SK
os.environ['USER_TABLE'] = PYTEST_TABLE_NAME os.environ['USER_TABLE'] = PYTEST_TABLE_NAME
os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['KONVIVA_API_URL'] = 'https://lms.saladeaula.digital' os.environ['KONVIVA_API_URL'] = 'https://lms.saladeaula.digital'

View File

@@ -1,6 +1,10 @@
// Users // Users
{"id": "123", "sk": "0"} {"id": "123", "sk": "0"}
// Courses
{"id": "432", "sk": "0", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"}
// Enrollments // Enrollments
{"id": "197991aa-52e2-4e0c-b2a4-a7c53bcfee02", "sk": "0", "progress": 10, "status": "IN_PROGRESS"} {"id": "197991aa-52e2-4e0c-b2a4-a7c53bcfee02", "sk": "0", "progress": 10, "status": "IN_PROGRESS"}
@@ -11,18 +15,15 @@
{"id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a", "sk": "SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS"} {"id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a", "sk": "SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS"}
{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "0", "progress": 80, "status": "IN_PROGRESS", "user": {"id": "123", "name": "Myles Kennedy"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"} {"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "0", "progress": 80, "status": "IN_PROGRESS", "user": {"id": "123", "name": "Myles Kennedy"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"}
{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"}
{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-04-06T11:07:32.762178-03:00"} {"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-04-06T11:07:32.762178-03:00"}
{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS"} {"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS"}
{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "SCHEDULE#SET_ACCESS_EXPIRED"} {"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "SCHEDULE#SET_ACCESS_EXPIRED"}
{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "0", "progress": 109, "status": "COMPLETED", "user": {"id": "321", "name": "Chester Bennington"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"} {"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "0", "progress": 109, "status": "COMPLETED", "user": {"id": "321", "name": "Chester Bennington"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"}
{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"}
{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-04-06T11:07:32.762178-03:00"} {"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-04-06T11:07:32.762178-03:00"}
{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "SCHEDULE#SET_AS_ARCHIVED"} {"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "SCHEDULE#SET_AS_ARCHIVED"}
{"id": "5db53b35-0bae-4907-afda-a213cb5bf651", "sk": "0", "progress": 0, "status": "PENDING", "user": {"id": "1234", "name": "Michael Jackinson"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"} {"id": "5db53b35-0bae-4907-afda-a213cb5bf651", "sk": "0", "progress": 0, "status": "PENDING", "user": {"id": "1234", "name": "Michael Jackinson"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"}
{"id": "5db53b35-0bae-4907-afda-a213cb5bf651", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"}
// To relate with the enrollment // To relate with the enrollment
{"id": "konviva", "sk": "123", "enrollment_id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a"} {"id": "konviva", "sk": "123", "enrollment_id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a"}

View File

@@ -113,7 +113,7 @@ def test_set_as_completed(
docx = next((x for x in r['items'] if x['sk'] == '0'), {}) docx = next((x for x in r['items'] if x['sk'] == '0'), {})
assert 'completed_at' in docx assert 'completed_at' in docx
assert len(r['items']) == 7 assert len(r['items']) == 6
assert any(item.get('sk') == 'LOCK' for item in r['items']) assert any(item.get('sk') == 'LOCK' for item in r['items'])
assert any( assert any(
item.get('sk') == 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS' item.get('sk') == 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS'