remove METADATA#COURSE

This commit is contained in:
2025-10-07 16:56:00 -03:00
parent 161b75db8d
commit 4fdf98a5b4
10 changed files with 40 additions and 31 deletions

View File

@@ -12,7 +12,13 @@ from layercake.dateutils import fromisoformat, now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from boto3clients import dynamodb_client, s3_client from boto3clients import dynamodb_client, s3_client
from config import BUCKET_NAME, ENROLLMENT_TABLE, PAPERFORGE_API, SIGNATURE_URI from config import (
BUCKET_NAME,
COURSE_TABLE,
ENROLLMENT_TABLE,
PAPERFORGE_API,
SIGNATURE_URI,
)
logger = Logger(__name__) logger = Logger(__name__)
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@@ -24,10 +30,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] new_image = event.detail['new_image']
now_ = now() now_ = now()
enrollment_id = new_image['id'] enrollment_id = new_image['id']
course_id = new_image['course']['id']
cert = dyn.collection.get_item( cert = dyn.collection.get_item(
KeyPair( KeyPair(
pk=new_image['id'], pk=course_id,
sk=SortKey('METADATA#COURSE', path_spec='cert', rename_key='cert'), sk=SortKey('0', path_spec='cert', rename_key='cert'),
table_name=COURSE_TABLE,
), ),
raise_on_error=False, raise_on_error=False,
default=False, default=False,

View File

@@ -24,7 +24,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
metadata = dyn.collection.get_items( metadata = dyn.collection.get_items(
TransactKey(new_image['id']) TransactKey(new_image['id'])
+ SortKey('METADATA#SUBSCRIPTION_COVERED', rename_key='subscription') + SortKey('METADATA#SUBSCRIPTION_COVERED', rename_key='subscription')
+ SortKey('METADATA#COURSE', rename_key='course')
+ SortKey( + SortKey(
'METADATA#DEDUPLICATION_WINDOW', 'METADATA#DEDUPLICATION_WINDOW',
path_spec='offset_days', path_spec='offset_days',
@@ -38,7 +37,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
flatten_top=False, flatten_top=False,
) )
user = User.model_validate(new_image['user']) user = User.model_validate(new_image['user'])
course = Course.model_validate(new_image['course'] | metadata['course']) course = Course.model_validate(new_image['course'])
subscription = metadata['subscription'] if 'subscription' in metadata else None subscription = metadata['subscription'] if 'subscription' in metadata else None
enrollment = Enrollment( enrollment = Enrollment(
id=uuid4(), id=uuid4(),

View File

@@ -1,12 +1,9 @@
from os import access
from aws_lambda_powertools import Logger from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_classes import ( from aws_lambda_powertools.utilities.data_classes import (
EventBridgeEvent, EventBridgeEvent,
event_source, event_source,
) )
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from glom import glom
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
@@ -26,14 +23,15 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
now_ = now() now_ = now()
enrollment_id = new_image['id'] enrollment_id = new_image['id']
user = new_image['user'] user = new_image['user']
course_name = glom(new_image, 'course.name') course_name = new_image['course']['name']
# 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( dyn.collection.get_item(
KeyPair( KeyPair(
pk=enrollment_id, pk=new_image['course']['id'],
sk=SortKey('METADATA#COURSE', path_spec='access_period'), sk=SortKey('0', path_spec='access_period'),
), ),
) )
) )

View File

@@ -51,15 +51,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'created_at': now_, 'created_at': now_,
} }
) )
transact.put(
item={
'id': new_image['id'],
'sk': 'METADATA#COURSE',
'created_at': now_,
'access_period': access_period,
'cert': course.get('cert', None),
}
)
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
return False return False

View File

@@ -26,7 +26,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:96 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:97
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo
@@ -314,6 +314,8 @@ Resources:
Policies: Policies:
- DynamoDBCrudPolicy: - DynamoDBCrudPolicy:
TableName: !Ref EnrollmentTable TableName: !Ref EnrollmentTable
- DynamoDBReadPolicy:
TableName: !Ref CourseTable
- S3WritePolicy: - S3WritePolicy:
BucketName: !Ref BucketName BucketName: !Ref BucketName
Events: Events:

View File

@@ -31,8 +31,6 @@ def test_enroll(
TransactKey('47ZxxcVBjvhDS5TE98tpfQ') TransactKey('47ZxxcVBjvhDS5TE98tpfQ')
+ SortKey('0') + SortKey('0')
+ SortKey('METADATA#DEDUPLICATION_WINDOW') + SortKey('METADATA#DEDUPLICATION_WINDOW')
+ SortKey('METADATA#COURSE')
) )
assert 'METADATA#COURSE' in result
assert 'METADATA#DEDUPLICATION_WINDOW' in result assert 'METADATA#DEDUPLICATION_WINDOW' in result

View File

@@ -20,6 +20,10 @@ def test_issue_cert(
'name': 'Jimi Hendrix', 'name': 'Jimi Hendrix',
'cpf': '74630003037', 'cpf': '74630003037',
}, },
'course': {
'id': '123',
'name': 'pytest',
},
'score': 79, 'score': 79,
'status': 'COMPLETED', 'status': 'COMPLETED',
} }

View File

@@ -33,4 +33,4 @@ def test_schedule_reminders(
r = dynamodb_persistence_layer.collection.query( r = dynamodb_persistence_layer.collection.query(
PartitionKey('14682b79-3df2-4351-9229-8b558af046a0') PartitionKey('14682b79-3df2-4351-9229-8b558af046a0')
) )
assert len(r['items']) == 4 assert len(r['items']) == 3

View File

@@ -11,7 +11,8 @@
{"id": "cpYSbBcie2NDbZhDKCxCih", "sk": "generated_items"} {"id": "cpYSbBcie2NDbZhDKCxCih", "sk": "generated_items"}
// Course // 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": "123", "sk": "0", "access_period": 360, "cert": {"exp_interval": 700, "s3_uri": "s3://saladeaula.digital/certs/samples/cipa-grau-de-risco-1.html"}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "pytest", "tenant_id": "*"}
{"id": "12334", "sk": "0", "access_period": 360}
{"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": "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": "*"} {"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": "*"}
{"id": "e1c44881-2fe3-484e-ada2-12b6bf5b9398", "sk": "0", "name": "NR-35 Segurança nos Trabalhos em Altura (Teórico)", "updated_at": "2025-08-22T00:00:24.431267-03:00", "access_period": 360, "created_at": "2024-12-30T00:11:33.088916-03:00", "metadata__konviva_class_id": 1, "tenant_id": "*", "cert": {"exp_interval": 700}, "metadata__unit_price": 119} {"id": "e1c44881-2fe3-484e-ada2-12b6bf5b9398", "sk": "0", "name": "NR-35 Segurança nos Trabalhos em Altura (Teórico)", "updated_at": "2025-08-22T00:00:24.431267-03:00", "access_period": 360, "created_at": "2024-12-30T00:11:33.088916-03:00", "metadata__konviva_class_id": 1, "tenant_id": "*", "cert": {"exp_interval": 700}, "metadata__unit_price": 119}
@@ -25,17 +26,14 @@
// Enrollment // Enrollment
{"id": "6437a282-6fe8-4e4d-9eb0-da1007238007", "sk": "0", "status": "IN_PROGRESS", "progress": 10} {"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} {"id": "845fe390-e3c3-4514-97f8-c42de0566cf0", "sk": "0", "status": "COMPLETED", "progress": 100}
{"id": "14682b79-3df2-4351-9229-8b558af046a0", "sk": "METADATA#COURSE", "access_period": 360}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "0", "status": "COMPLETED", "score": 100, "course": {"name": "CIPA Grau de Risco 1"}, "user": {"name": "Kurt Cobain"}, "issued_cert": {"s3_uri": "s3://saladeaula.digital/issuedcerts/1ee108ae-67d4-4545-bf6d-4e641cdaa4e0.pdf"}} {"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "0", "status": "COMPLETED", "score": 100, "course": {"id": "123", "name": "CIPA Grau de Risco 1"}, "user": {"name": "Kurt Cobain"}, "issued_cert": {"s3_uri": "s3://saladeaula.digital/issuedcerts/1ee108ae-67d4-4545-bf6d-4e641cdaa4e0.pdf"}}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "METADATA#COURSE", "cert": {"s3_uri": "s3://saladeaula.digital/certs/samples/cipa-grau-de-risco-1.html", "exp_interval": 700}}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "STARTED", "started_at": "2025-08-24T01:44:42.703012-03:06"} {"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "STARTED", "started_at": "2025-08-24T01:44:42.703012-03:06"}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "COMPLETED", "completed_at": "2025-08-31T21:59:10.842467-03:00"} {"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "COMPLETED", "completed_at": "2025-08-31T21:59:10.842467-03:00"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "0"} {"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "0"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#SUBSCRIPTION_COVERED", "updated_at": "2025-09-09T15:20:17.187461-03:00", "billing_day": 1, "org_id": "123", "created_at": "2025-09-09T15:20:14.021500-03:00", "billing_period": "START#2025-09-01#END#2025-09-30"} {"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#SUBSCRIPTION_COVERED", "updated_at": "2025-09-09T15:20:17.187461-03:00", "billing_day": 1, "org_id": "123", "created_at": "2025-09-09T15:20:14.021500-03:00", "billing_period": "START#2025-09-01#END#2025-09-30"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#COURSE", "access_period": 360, "created_at": "2025-09-09T09:11:29.292760-03:00", "cert": {"exp_interval": 360}}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "konviva", "class_id": 34, "user_id": 26943, "created_at": "2025-09-09T09:11:29.315247-03:00", "enrollment_id": 244488}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-09-11T09:00:45.923035-03:00"} {"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-09-11T09:00:45.923035-03:00"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "konviva", "class_id": 34, "user_id": 26943, "created_at": "2025-09-09T09:11:29.315247-03:00", "enrollment_id": 244488}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "tenant", "org_id": "123", "name": "EDUSEG", "create_date": "2025-09-12T17:11:00.556907-03:00"} {"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "tenant", "org_id": "123", "name": "EDUSEG", "create_date": "2025-09-12T17:11:00.556907-03:00"}

View File

@@ -501,7 +501,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.9.14" version = "0.10.0"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },
@@ -518,6 +518,7 @@ dependencies = [
{ name = "pycpfcnpj" }, { name = "pycpfcnpj" },
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "python-multipart" },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "smart-open", extra = ["s3"] }, { name = "smart-open", extra = ["s3"] },
@@ -541,6 +542,7 @@ requires-dist = [
{ name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pycpfcnpj", specifier = ">=1.8" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
{ name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "python-multipart", specifier = ">=0.0.20" },
{ name = "pytz", specifier = ">=2025.1" }, { name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" }, { name = "requests", specifier = ">=2.32.3" },
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
@@ -835,6 +837,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
] ]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"