remove archived and expired

This commit is contained in:
2025-09-18 12:24:21 -03:00
parent 0ebf108a94
commit db63dfa64d
8 changed files with 134 additions and 232 deletions

View File

@@ -26,7 +26,7 @@ dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
def postback():
json_body = app.current_event.json_body
status = json_body['status']
event_name = json_body['event_name']
score = round(Decimal(json_body['APROVEITAMENTO']), 2)
progress = round(Decimal(json_body['ANDAMENTO']), 2)
enrollment_id = dyn.collection.get_item(
@@ -34,16 +34,29 @@ def postback():
# Post-migration: uncomment the following line
# pk='KONVIVA',
pk='konviva',
sk=SortKey(json_body['ID_MATRICULA'], path_spec='enrollment_id'),
sk=SortKey(
json_body['ID_MATRICULA'],
path_spec='enrollment_id',
),
),
exc_cls=EnrollmentNotFoundError,
)
if status == 'IN_PROGRESS':
update_progress(enrollment_id, progress, dynamodb_persistence_layer=dyn)
if status == 'COMPLETED':
set_score(enrollment_id, score, progress, dynamodb_persistence_layer=dyn)
# Make sure webhooks send the correct event names
match event_name:
case 'UPDATING':
update_progress(
enrollment_id,
progress,
dynamodb_persistence_layer=dyn,
)
case 'COMPLETED':
set_score(
enrollment_id,
score,
progress,
dynamodb_persistence_layer=dyn,
)
return Response(status_code=HTTPStatus.NO_CONTENT)

View File

@@ -1,4 +1,3 @@
from datetime import datetime, timedelta
from decimal import Decimal
from aws_lambda_powertools.event_handler.exceptions import (
@@ -7,7 +6,7 @@ from aws_lambda_powertools.event_handler.exceptions import (
)
from botocore.args import logger
from glom import glom
from layercake.dateutils import fromisoformat, now, ttl
from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey
from layercake.strutils import md5_hash
@@ -124,56 +123,34 @@ def set_score(
rename_key='dedup_window_offset_days',
),
)
now_ = now()
user_id = enrollment['user']['id']
course_id = glom(enrollment, 'course.id')
created_at: datetime = fromisoformat(enrollment['created_at']) # type: ignore
access_period = created_at + timedelta(
days=int(glom(enrollment, 'metadata__course.access_period'))
)
try:
match score >= 70, now_ > access_period:
case True, True:
# Got a score of 70 or higher, but the access period has expired
return _set_status_as_archived(
id,
dynamodb_persistence_layer=dynamodb_persistence_layer,
)
case True, False:
# Got a score of 70 or higher, and still within the access period
return _set_status_as_completed(
id,
score,
progress=progress,
user_id=user_id,
course_id=course_id,
cert_exp_interval=int(
glom(
enrollment, 'metadata__course.cert.exp_interval', default=0
)
),
dedup_window_offset_days=int(
enrollment['dedup_window_offset_days']
),
dynamodb_persistence_layer=dynamodb_persistence_layer,
)
case False, True:
# Got a score below 70, and the access period has expired
return _set_status_as_expired(
id,
dynamodb_persistence_layer=dynamodb_persistence_layer,
)
case _:
# Got a score below 70, and still within the access period
return _set_status_as_failed(
id,
score,
progress=progress,
user_id=user_id,
course_id=course_id,
dynamodb_persistence_layer=dynamodb_persistence_layer,
)
if score >= 70:
# Got a score of 70 or higher
return _set_status_as_completed(
id,
score,
progress=progress,
user_id=user_id,
course_id=course_id,
cert_exp_interval=int(
glom(enrollment, 'metadata__course.cert.exp_interval', default=0)
),
dedup_window_offset_days=int(enrollment['dedup_window_offset_days']),
dynamodb_persistence_layer=dynamodb_persistence_layer,
)
# Got a score below 70
return _set_status_as_failed(
id,
score,
progress=progress,
user_id=user_id,
course_id=course_id,
dynamodb_persistence_layer=dynamodb_persistence_layer,
)
except EnrollmentConflictError as err:
logger.exception(err)
raise
@@ -193,15 +170,15 @@ def _set_status_as_completed(
) -> bool:
now_ = now()
lock_hash = md5_hash(f'{user_id}{course_id}')
archive_ttl = ttl(
cert_exp_ttl = ttl(
start_dt=now_,
days=cert_exp_interval,
)
cert_expiration_reminder_ttl = ttl(
cert_exp_reminder_ttl = ttl(
start_dt=now_,
days=cert_exp_interval - 30,
)
deduplication_lock_ttl = ttl(
dedup_lock_ttl = ttl(
start_dt=now_,
days=cert_exp_interval - dedup_window_offset_days,
)
@@ -209,8 +186,10 @@ def _set_status_as_completed(
with dynamodb_persistence_layer.transact_writer() as transact:
transact.update(
key=KeyPair(pk=id, sk='0'),
update_expr='SET #status = :completed, progress = :progress, \
score = :score, updated_at = :updated_at',
update_expr='SET #status = :completed, \
progress = :progress, \
score = :score, \
updated_at = :updated_at',
cond_expr='#status = :in_progress',
expr_attr_names={'#status': 'status'},
expr_attr_values={
@@ -236,15 +215,15 @@ def _set_status_as_completed(
item={
'id': id,
'sk': 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS',
'ttl': cert_expiration_reminder_ttl,
'ttl': cert_exp_ttl,
'created_at': now_,
}
)
transact.put(
item={
'id': id,
'sk': 'SCHEDULE#SET_AS_ARCHIVED',
'ttl': archive_ttl,
'sk': 'SCHEDULE#SET_CERT_EXPIRED',
'ttl': cert_exp_reminder_ttl,
'created_at': now_,
}
)
@@ -252,7 +231,7 @@ def _set_status_as_completed(
item={
'id': id,
'sk': 'LOCK',
'ttl': deduplication_lock_ttl,
'ttl': dedup_lock_ttl,
'hash': lock_hash,
'created_at': now_,
}
@@ -262,7 +241,7 @@ def _set_status_as_completed(
'id': 'LOCK',
'sk': lock_hash,
'enrollment_id': id,
'ttl': deduplication_lock_ttl,
'ttl': dedup_lock_ttl,
'created_at': now_,
}
)
@@ -274,12 +253,6 @@ def _set_status_as_completed(
sk='CANCEL_POLICY',
)
)
transact.delete(
key=KeyPair(
pk=id,
sk='SCHEDULE#SET_AS_EXPIRED',
)
)
transact.delete(
key=KeyPair(
pk=id,
@@ -312,8 +285,11 @@ def _set_status_as_failed(
with dynamodb_persistence_layer.transact_writer() as transact:
transact.update(
key=KeyPair(pk=id, sk='0'),
update_expr='SET #status = :failed, progress = :progress, \
score = :score, updated_at = :updated_at',
update_expr='SET #status = :failed, \
progress = :progress, \
score = :score, \
access_expired = :access_expired, \
updated_at = :updated_at',
cond_expr='#status = :in_progress',
expr_attr_names={'#status': 'status'},
expr_attr_values={
@@ -321,6 +297,7 @@ def _set_status_as_failed(
':in_progress': 'IN_PROGRESS',
':score': score,
':progress': progress,
':access_expired': True,
':updated_at': now_,
},
exc_cls=EnrollmentConflictError,
@@ -343,7 +320,7 @@ def _set_status_as_failed(
transact.delete(
key=KeyPair(
pk=id,
sk='SCHEDULE#SET_AS_EXPIRED',
sk='SCHEDULE#SET_ACCESS_EXPIRED',
)
)
transact.delete(
@@ -369,90 +346,6 @@ def _set_status_as_failed(
return True
def _set_status_as_archived(
id: str,
*,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
):
now_ = now()
with dynamodb_persistence_layer.transact_writer() as transact:
transact.update(
key=KeyPair(pk=id, sk='0'),
update_expr='SET #status = :archived, updated_at = :updated_at',
cond_expr='#status = :completed',
expr_attr_names={'#status': 'status'},
expr_attr_values={
':archived': 'ARCHIVED',
':completed': 'COMPLETED',
':updated_at': now_,
},
exc_cls=EnrollmentConflictError,
)
transact.put(
item={
'id': id,
'sk': 'ARCHIVED',
'archived_at': now_,
},
cond_expr='attribute_not_exists(sk)',
)
# Remove events that no longer apply
transact.delete(
key=KeyPair(
pk=id,
sk='SCHEDULE#SET_AS_ARCHIVED',
)
)
return True
def _set_status_as_expired(
id: str,
*,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
):
now_ = now()
with dynamodb_persistence_layer.transact_writer() as transact:
transact.update(
key=KeyPair(pk=id, sk='0'),
update_expr='SET #status = :expired, updated_at = :updated_at',
cond_expr='#status = :in_progress OR #status = :pending',
expr_attr_names={
'#status': 'status',
},
expr_attr_values={
':pending': 'PENDING',
':in_progress': 'IN_PROGRESS',
':expired': 'EXPIRED',
':updated_at': now_,
},
exc_cls=EnrollmentConflictError,
)
transact.put(
item={
'id': id,
'sk': 'EXPIRED',
'expired_at': now_,
},
cond_expr='attribute_not_exists(sk)',
)
# Remove events and policies that no longer apply
transact.delete(
key=KeyPair(
pk=id,
sk='CANCEL_POLICY',
),
)
transact.delete(
key=KeyPair(
pk=id,
sk='SCHEDULE#SET_AS_EXPIRED',
)
)
class EnrollmentNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('Enrollment not found')

View File

@@ -11,14 +11,14 @@ from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE
logger = Logger(__name__)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
dyn = 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']
data = enrollment_layer.get_item(KeyPair(new_image['id'], 'konviva'))
data = dyn.get_item(KeyPair(new_image['id'], 'konviva'))
try:
result = konviva.cancel_enrollment(data['enrollment_id'])

View File

@@ -13,7 +13,7 @@ from boto3clients import dynamodb_client
from config import USER_TABLE
logger = Logger(__name__)
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
class UserNotFoundError(Exception): ...
@@ -43,7 +43,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
except Exception:
raise
with user_layer.transact_writer() as transact:
with dyn.transact_writer() as transact:
transact.update(
key=KeyPair(new_image['id'], '0'),
update_expr='SET metadata__konviva_user_id = :user_id, \

View File

@@ -12,7 +12,7 @@ from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE
logger = Logger(__name__)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@event_source(data_class=EventBridgeEvent)
@@ -32,7 +32,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
logger.exception(err)
return False
with enrollment_layer.transact_writer() as transact:
with dyn.transact_writer() as transact:
transact.update(
key=KeyPair(new_image['id'], 'konviva'),
update_expr='SET enrollment_id = :enrollment_id',

View File

@@ -4,14 +4,10 @@ from aws_lambda_powertools.utilities.data_classes import (
event_source,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer
import konviva
from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE
logger = Logger(__name__)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@event_source(data_class=EventBridgeEvent)