fix enroll and reenroll relationship

This commit is contained in:
2025-10-14 18:11:24 -03:00
parent 466ff824dd
commit a7e5a0a528
20 changed files with 125 additions and 89 deletions

View File

@@ -1,4 +1,7 @@
from abc import ABC
from dataclasses import dataclass
from datetime import timedelta
from enum import Enum
from typing import NotRequired, Self, TypedDict
from layercake.dateutils import now, ttl
@@ -32,15 +35,16 @@ Subscription = TypedDict(
)
class LinkedEntity(str):
def __new__(cls, id: str, type: str) -> Self:
return super().__new__(cls, '#'.join([type.lower(), id]))
class Kind(str, Enum):
ORDER = 'ORDER'
ENROLLMENT = 'ENROLLMENT'
def __init__(self, id: str, type: str) -> None:
# __init__ is used to store the parameters for later reference.
# For immutable types like str, __init__ cannot change the instance's value.
self.id = id
self.type = type
@dataclass(frozen=True)
class LinkedEntity(ABC):
id: str
kind: Kind
table_name: str | None = None
class DeduplicationConflictError(Exception):
@@ -76,29 +80,27 @@ def enroll(
)
# Relationships between this enrollment and its related entities
for parent_entity in linked_entities:
perent_id = parent_entity.id
entity_sk = f'LINKED_ENTITIES#{parent_entity.type}'
keyprefix = parent_entity.type.lower()
for entity in linked_entities:
# Parent knows the child
transact.put(
item={
'id': perent_id,
'sk': f'{entity_sk}#CHILD',
'id': entity.id,
'sk': f'LINKED_ENTITIES#{entity.kind.value}#CHILD',
'created_at': now_,
f'{keyprefix}_id': enrollment.id,
'enrollment_id': enrollment.id,
},
cond_expr='attribute_not_exists(sk)',
table_name=entity.table_name,
)
keyprefix = entity.kind.value.lower()
# Child knows the parent
transact.put(
item={
'id': enrollment.id,
'sk': f'{entity_sk}#PARENT',
'sk': f'LINKED_ENTITIES#{entity.kind.value}#PARENT',
'created_at': now_,
f'{keyprefix}_id': perent_id,
f'{keyprefix}_id': entity.id,
},
cond_expr='attribute_not_exists(sk)',
)

View File

@@ -81,6 +81,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
class Course:
id: str
name: str
access_period: int
def _get_courses(ids: set) -> tuple[Course, ...]:
@@ -93,6 +94,7 @@ def _get_courses(ids: set) -> tuple[Course, ...]:
Course(
id=idx,
name=obj['name'],
access_period=obj['access_period'],
)
for idx, obj in result.items()
)

View File

@@ -18,7 +18,11 @@ from layercake.dynamodb import (
from boto3clients import dynamodb_client
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
from enrollment import LinkedEntity, enroll
from enrollment import (
Kind,
LinkedEntity,
enroll,
)
from schemas import Course, Enrollment, User
logger = Logger(__name__)
@@ -89,7 +93,15 @@ def _handler(record: Course, context: dict) -> Enrollment:
enrollment,
persistence_layer=enrollment_layer,
deduplication_window={'offset_days': 90},
linked_entities=frozenset({LinkedEntity(context['order_id'], 'ORDER')}),
linked_entities=frozenset(
{
LinkedEntity(
id=context['order_id'],
kind=Kind.ORDER,
table_name=ORDER_TABLE,
),
}
),
)
return enrollment

View File

@@ -56,6 +56,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
)
try:
if 's3_uri' not in cert:
raise ValueError('Template URI is missing')
# Send template URI and data to Paperforge API to generate a PDF
r = requests.post(
PAPERFORGE_API,
@@ -79,10 +82,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
),
},
),
timeout=5,
)
r.raise_for_status()
object_key = f'issuedcerts/{enrollment_id}.pdf'
object_key = f'certs/{enrollment_id}.pdf'
s3_uri = f's3://{BUCKET_NAME}/{object_key}'
s3_client.put_object(
@@ -93,10 +97,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
)
logger.debug(f'PDF uploaded successfully to {s3_uri}')
except KeyError:
except ValueError as exc:
# PDF generation fails if template URI is missing
s3_uri = None
logger.debug('Template URI is missing')
logger.exception(exc)
except requests.exceptions.RequestException as exc:
logger.exception(exc)
raise

View File

@@ -10,7 +10,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, SortKey, TransactKey
from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE
from enrollment import LinkedEntity, enroll
from enrollment import Kind, LinkedEntity, enroll
from schemas import Course, Enrollment, User
logger = Logger(__name__)
@@ -52,7 +52,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'offset_days': metadata['dedup_window_offset_days'],
},
linked_entities=frozenset(
{LinkedEntity(new_image['id'], 'ENROLLMENT')},
{
LinkedEntity(
id=new_image['id'],
kind=Kind.ENROLLMENT,
),
},
),
persistence_layer=dyn,
)

View File

@@ -29,8 +29,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
pk=old_image['id'],
sk='0',
),
update_expr='SET access_expired = :true, \
updated_at = :now',
update_expr='SET access_expired = :true, updated_at = :now',
expr_attr_values={
':true': True,
':now': now_,

View File

@@ -308,6 +308,7 @@ Resources:
Type: AWS::Serverless::Function
Properties:
Handler: events.issue_cert.lambda_handler
# Timeout: 30
LoggingConfig:
LogGroup: !Ref EventLog
Policies:
@@ -331,10 +332,10 @@ Resources:
old_image:
status: [IN_PROGRESS]
EventCertReportingAppendCertFunction:
EventReportingAppendCertFunction:
Type: AWS::Serverless::Function
Properties:
Handler: events.cert_reporting.append_cert.lambda_handler
Handler: events.reporting.append_cert.lambda_handler
LoggingConfig:
LogGroup: !Ref EventLog
Policies:
@@ -346,23 +347,19 @@ Resources:
Properties:
Pattern:
resources: [!Ref EnrollmentTable]
detail-type: [MODIFY]
detail:
keys:
sk: ["0"]
new_image:
status: [COMPLETED]
cert:
exists: true
org_id:
exists: true
old_image:
cert:
exists: false
- exists: true
EventCertReportingSendReportEmailFunction:
EventReportingSendReportEmailFunction:
Type: AWS::Serverless::Function
Properties:
Handler: events.cert_reporting.send_report_email.lambda_handler
Handler: events.reporting.send_report_email.lambda_handler
LoggingConfig:
LogGroup: !Ref EventLog
Policies:

View File

@@ -20,6 +20,7 @@ def pytest_configure():
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
os.environ['LOG_LEVEL'] = 'DEBUG'
@dataclass

View File

@@ -1,6 +1,6 @@
from datetime import timedelta
import app.events.cert_reporting.append_cert as app
import app.events.reporting.append_cert as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dateutils import now
from layercake.dynamodb import (

View File

@@ -1,4 +1,4 @@
import app.events.cert_reporting.send_report_email as app
import app.events.reporting.send_report_email as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import (
DynamoDBPersistenceLayer,

View File

@@ -1,6 +1,6 @@
import app.events.enroll as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
def test_enroll(
@@ -8,12 +8,26 @@ def test_enroll(
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
lambda_context: LambdaContext,
):
order_id = 'cpYSbBcie2NDbZhDKCxCih'
event = {
'detail': {
'new_image': {
'id': 'cpYSbBcie2NDbZhDKCxCih',
'id': order_id,
'sk': 'generated_items',
}
}
}
assert app.lambda_handler(event, lambda_context) # type: ignore
# Parent knows the child
order_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair(order_id, 'LINKED_ENTITIES#ORDER#CHILD')
)
assert order_entity
# Child knows the parent
enrollment_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair(order_entity['enrollment_id'], 'LINKED_ENTITIES#ORDER#PARENT'),
)
assert enrollment_entity['order_id'] == order_id

View File

@@ -30,19 +30,19 @@ def test_reenroll(
assert app.lambda_handler(event, lambda_context) # type: ignore
# Parent knows the child
child_entity = dynamodb_persistence_layer.collection.get_item(
current_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair(
pk=parent_id,
sk='LINKED_ENTITIES#ENROLLMENT#CHILD',
)
)
assert child_entity
assert current_entity
# Child knows the parent
parent_entity = dynamodb_persistence_layer.collection.get_item(
new_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair(
pk=child_entity['enrollment_id'],
pk=current_entity['enrollment_id'],
sk='LINKED_ENTITIES#ENROLLMENT#PARENT',
)
)
assert parent_entity['enrollment_id'] == parent_id
assert new_entity['enrollment_id'] == parent_id