fix duplicate user

This commit is contained in:
2026-01-28 10:43:46 -03:00
parent 428006cfac
commit fc14d425f2
14 changed files with 151 additions and 141 deletions

View File

@@ -1,9 +1,5 @@
from abc import ABC
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Any, Literal, TypedDict
from uuid import uuid4
from typing import Literal, TypedDict
from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
@@ -27,7 +23,6 @@ class User(BaseModel):
id: UUID4 | str
name: NameStr
email: EmailStr
email_verified: bool = False
cpf: CpfStr | None = None
@@ -44,18 +39,6 @@ class Enrollment(BaseModel):
progress: int = Field(default=0, ge=0, le=100)
status: Literal['PENDING'] = 'PENDING'
def model_dump(
self,
exclude=None,
*args,
**kwargs,
) -> dict[str, Any]:
return super().model_dump(
exclude={'user': {'email_verified'}},
*args,
**kwargs,
)
Org = TypedDict('Org', {'org_id': str, 'name': str})
@@ -75,18 +58,6 @@ Subscription = TypedDict(
)
class Kind(str, Enum):
ORDER = 'ORDER'
ENROLLMENT = 'ENROLLMENT'
@dataclass(frozen=True)
class LinkedEntity(ABC):
id: str
kind: Kind
table_name: str | None = None
class DeduplicationConflictError(Exception):
def __init__(self, *args):
super().__init__('Enrollment already exists')
@@ -121,7 +92,7 @@ def enroll(
created_by: CreatedBy | None = None,
scheduled_at: datetime | None = None,
seat: Seat | None = None,
linked_entities: frozenset[LinkedEntity] = frozenset(),
parent_entity: str | None = None,
deduplication_window: DeduplicationWindow | None = None,
persistence_layer: DynamoDBPersistenceLayer,
) -> bool:
@@ -156,43 +127,51 @@ def enroll(
)
if seat:
order_id = seat['order_id']
transact.condition(
key=KeyPair(str(seat['order_id']), '0'),
key=KeyPair(order_id, '0'),
cond_expr='attribute_exists(sk)',
exc_cls=OrderNotFoundError,
table_name=ORDER_TABLE,
)
transact.put(
item={
'id': seat['order_id'],
'id': order_id,
'sk': f'ENROLLMENT#{enrollment.id}',
'course': course.model_dump(),
'user': user.model_dump(),
'user': user.model_dump(exclude={'cpf'}),
'status': 'EXECUTED',
'executed_at': now_,
'created_at': now_,
},
table_name=ORDER_TABLE,
)
# Relationships between this enrollment and its related entities
for entity in linked_entities:
# Parent knows the child
# Enrollment should know where it comes from
transact.put(
item={
'id': entity.id,
'sk': f'LINKED_ENTITIES#CHILD#ENROLLMENT#{enrollment.id}',
'id': enrollment.id,
'sk': f'LINKED_ENTITY#PARENT#ORDER#{order_id}',
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
)
if parent_entity:
# Parent knows the child
transact.put(
item={
'id': parent_entity,
'sk': f'LINKED_ENTITY#CHILD#ENROLLMENT#{enrollment.id}',
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
table_name=entity.table_name,
)
# Child knows the parent
transact.put(
item={
'id': enrollment.id,
'sk': f'LINKED_ENTITIES#PARENT#{entity.kind.value}#{entity.id}',
'sk': f'LINKED_ENTITY#PARENT#ENROLLMENT#{parent_entity}',
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',

View File

@@ -21,8 +21,6 @@ from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
from enrollment import (
Course,
Enrollment,
Kind,
LinkedEntity,
User,
enroll,
)
@@ -91,19 +89,7 @@ def _handler(course: Course, context: dict) -> Enrollment:
course=course,
)
enroll(
enrollment,
persistence_layer=enrollment_layer,
linked_entities=frozenset(
{
LinkedEntity(
id=context['order_id'],
kind=Kind.ORDER,
table_name=ORDER_TABLE,
),
}
),
)
enroll(enrollment, persistence_layer=enrollment_layer)
return enrollment

View File

@@ -14,8 +14,6 @@ from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE, ORDER_TABLE
from enrollment import (
Enrollment,
Kind,
LinkedEntity,
Seat,
Subscription,
enroll,
@@ -54,21 +52,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
)
try:
# The enrollment must know its source
linked_entities = (
frozenset(
{
LinkedEntity(
id=seat['order_id'],
kind=Kind.ORDER,
table_name=ORDER_TABLE,
),
},
)
if seat
else frozenset()
)
enroll(
enrollment,
org={
@@ -82,7 +65,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
scheduled_at=datetime.fromisoformat(old_image['scheduled_at']),
# Transfer the deduplication window if it exists
deduplication_window={'offset_days': offset_days} if offset_days else None,
linked_entities=linked_entities,
persistence_layer=dyn,
)
@@ -105,17 +87,49 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
),
)
except Exception as exc:
dyn.put_item(
item={
'id': old_image['id'],
'sk': f'{sk}#FAILED',
'cause': {
'type': type(exc).__name__,
'message': str(exc),
},
'snapshot': old_image,
'created_at': now_,
}
)
with dyn.transact_writer() as transact:
transact.put(
item={
'id': old_image['id'],
'sk': f'{sk}#FAILED',
'cause': {
'type': type(exc).__name__,
'message': str(exc),
},
'snapshot': old_image,
'created_at': now_,
}
)
if seat:
order_id = seat['order_id']
transact.update(
key=KeyPair(
pk=order_id,
sk=f'ENROLLMENT#{enrollment.id}',
),
cond_expr='attribute_exists(sk) AND #status = :scheduled',
update_expr='SET #status = :rollback, \
rollback_at = :now, \
reason = :reason',
expr_attr_names={
'#status': 'status',
},
expr_attr_values={
':rollback': 'ROLLBACK',
':scheduled': 'SCHEDULED',
':reason': 'DEDUPLICATION',
':now': now_,
},
table_name=ORDER_TABLE,
)
transact.put(
item={
'id': f'SEAT#ORG#{org_id}',
'sk': f'ORDER#{order_id}#ENROLLMENT#{uuid4()}',
'course': enrollment.course.model_dump(),
'created_at': now_,
}
)
return True

View File

@@ -13,8 +13,6 @@ from config import ENROLLMENT_TABLE
from enrollment import (
Course,
Enrollment,
Kind,
LinkedEntity,
SubscriptionFrozenError,
User,
enroll,
@@ -64,14 +62,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
# Reuse the deduplication window if it exists
deduplication_window={'offset_days': offset_days} if offset_days else None,
# The enrollment must know its source
linked_entities=frozenset(
{
LinkedEntity(
id=new_image['id'],
kind=Kind.ENROLLMENT,
),
},
),
parent_entity=new_image['id'],
persistence_layer=dyn,
)
except SubscriptionFrozenError:

View File

@@ -30,7 +30,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
key=KeyPair(
pk=order_id,
sk=f'ENROLLMENT#{enrollment_id}',
table_name=ORDER_TABLE,
),
cond_expr='attribute_exists(sk) AND #status = :scheduled',
update_expr='SET #status = :rollback, \