remove deduplication as default
This commit is contained in:
@@ -2,12 +2,18 @@ AWSTemplateFormatVersion: "2010-09-09"
|
||||
Transform: "AWS::Serverless-2016-10-31"
|
||||
|
||||
Parameters:
|
||||
UsersTable:
|
||||
Type: String
|
||||
Default: betaeducacao-prod-users_d2o3r5gmm4it7j
|
||||
CourseTable:
|
||||
Type: String
|
||||
Default: saladeaula_courses
|
||||
EnrollmentTable:
|
||||
Type: String
|
||||
Default: betaeducacao-prod-enrollments
|
||||
OrderTable:
|
||||
Type: String
|
||||
Default: betaeducacao-prod-orders
|
||||
BucketName:
|
||||
Type: String
|
||||
Default: saladeaula.digital
|
||||
@@ -66,10 +72,14 @@ Resources:
|
||||
LoggingConfig:
|
||||
LogGroup: !Ref HttpLog
|
||||
Policies:
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref UserTable
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref CourseTable
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref EnrollmentTable
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref OrderTable
|
||||
- S3CrudPolicy:
|
||||
BucketName: !Ref BucketName
|
||||
Events:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
|
||||
DEDUP_WINDOW_OFFSET_DAYS = 90
|
||||
|
||||
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
|
||||
ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
|
||||
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
||||
|
||||
@@ -5,9 +5,10 @@ from enum import Enum
|
||||
from typing import NotRequired, TypedDict
|
||||
|
||||
from layercake.dateutils import now, ttl
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||
from layercake.strutils import md5_hash
|
||||
|
||||
from config import DEDUP_WINDOW_OFFSET_DAYS
|
||||
from schemas import Enrollment
|
||||
|
||||
Org = TypedDict(
|
||||
@@ -124,8 +125,11 @@ def enroll(
|
||||
|
||||
# Prevents the user from enrolling in the same course again until
|
||||
# the deduplication window expires or is removed.
|
||||
if deduplication_window:
|
||||
offset_days = int(deduplication_window['offset_days'])
|
||||
offset_days = (
|
||||
int(deduplication_window['offset_days'])
|
||||
if deduplication_window
|
||||
else DEDUP_WINDOW_OFFSET_DAYS
|
||||
)
|
||||
ttl_ = ttl(
|
||||
start_dt=now_,
|
||||
days=course.access_period - offset_days,
|
||||
@@ -150,7 +154,9 @@ def enroll(
|
||||
'ttl': ttl_,
|
||||
},
|
||||
)
|
||||
# Deduplication window can be recalculated if needed
|
||||
|
||||
# The deduplication window can be recalculated based on user settings.
|
||||
if deduplication_window:
|
||||
transact.put(
|
||||
item={
|
||||
'id': enrollment.id,
|
||||
@@ -159,11 +165,5 @@ def enroll(
|
||||
'created_at': now_,
|
||||
},
|
||||
)
|
||||
else:
|
||||
transact.condition(
|
||||
key=KeyPair('LOCK', lock_hash),
|
||||
cond_expr='attribute_not_exists(sk)',
|
||||
exc_cls=DeduplicationConflictError,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -92,7 +92,6 @@ def _handler(record: Course, context: dict) -> Enrollment:
|
||||
enroll(
|
||||
enrollment,
|
||||
persistence_layer=enrollment_layer,
|
||||
deduplication_window={'offset_days': 90},
|
||||
linked_entities=frozenset(
|
||||
{
|
||||
LinkedEntity(
|
||||
|
||||
@@ -3,7 +3,7 @@ from datetime import datetime, timedelta
|
||||
from typing import NotRequired, TypedDict
|
||||
|
||||
import requests
|
||||
from aws_lambda_powertools import Logger
|
||||
from aws_lambda_powertools import Logger, Tracer
|
||||
from aws_lambda_powertools.utilities.data_classes import (
|
||||
EventBridgeEvent,
|
||||
event_source,
|
||||
@@ -21,12 +21,14 @@ from config import (
|
||||
PAPERFORGE_API,
|
||||
)
|
||||
|
||||
tracer = Tracer()
|
||||
logger = Logger(__name__)
|
||||
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
@logger.inject_lambda_context
|
||||
@tracer.capture_lambda_handler
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
new_image = event.detail['new_image']
|
||||
now_ = now()
|
||||
|
||||
@@ -23,18 +23,25 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
new_image = event.detail['new_image']
|
||||
metadata = dyn.collection.get_items(
|
||||
TransactKey(new_image['id'])
|
||||
+ SortKey('METADATA#SUBSCRIPTION_COVERED', rename_key='subscription')
|
||||
+ SortKey(
|
||||
'METADATA#SUBSCRIPTION_COVERED',
|
||||
rename_key='subscription',
|
||||
)
|
||||
+ SortKey(
|
||||
'METADATA#DEDUPLICATION_WINDOW',
|
||||
path_spec='offset_days',
|
||||
rename_key='dedup_window_offset_days',
|
||||
)
|
||||
+ SortKey('ORG', rename_key='org'),
|
||||
+ SortKey(
|
||||
'ORG',
|
||||
rename_key='org',
|
||||
),
|
||||
flatten_top=False,
|
||||
)
|
||||
user = User.model_validate(new_image['user'])
|
||||
course = Course.model_validate(new_image['course'])
|
||||
subscription = metadata['subscription'] if 'subscription' in metadata else None
|
||||
offset_days = metadata.get('dedup_window_offset_days', None)
|
||||
enrollment = Enrollment(
|
||||
id=uuid4(),
|
||||
course=course,
|
||||
@@ -45,9 +52,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
enrollment,
|
||||
org=metadata.get('org', None),
|
||||
subscription=subscription,
|
||||
deduplication_window={
|
||||
'offset_days': metadata['dedup_window_offset_days'],
|
||||
},
|
||||
deduplication_window={'offset_days': offset_days} if offset_days else None,
|
||||
linked_entities=frozenset(
|
||||
{
|
||||
LinkedEntity(
|
||||
|
||||
@@ -31,12 +31,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool | No
|
||||
)
|
||||
|
||||
target_month = expires_at.strftime('%Y-%m')
|
||||
now_ = now()
|
||||
pk = f'CERT#REPORTING#ORG#{org_id}'
|
||||
now_ = now(tz)
|
||||
pk = f'CERT_REPORTING#ORG#{org_id}'
|
||||
|
||||
try:
|
||||
if now_ > expires_at:
|
||||
raise InvalidDateError()
|
||||
raise InvalidDateError('Invalid date')
|
||||
|
||||
# The reporting month is the month before the certificate expires
|
||||
report_month = (expires_at.replace(day=1) - timedelta(days=1)).replace(day=1)
|
||||
@@ -52,7 +52,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool | No
|
||||
'created_at': now_,
|
||||
},
|
||||
cond_expr='attribute_not_exists(sk)',
|
||||
exc_cls=ReportingConflictError,
|
||||
exc_cls=ReportExistsError,
|
||||
)
|
||||
transact.put(
|
||||
item={
|
||||
@@ -64,7 +64,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool | No
|
||||
},
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
logger.info(exc)
|
||||
|
||||
try:
|
||||
dyn.put_item(
|
||||
@@ -89,4 +89,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool | No
|
||||
class InvalidDateError(Exception): ...
|
||||
|
||||
|
||||
class ReportingConflictError(Exception): ...
|
||||
class ReportExistsError(Exception):
|
||||
def __init__(self, *args: object) -> None:
|
||||
super().__init__('Report already exists')
|
||||
|
||||
@@ -49,7 +49,7 @@ user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||
@logger.inject_lambda_context
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
old_image = event.detail['old_image']
|
||||
# Key pattern `CERT#REPORTING#ORG#{org_id}`
|
||||
# Key pattern `CERT_REPORTING#ORG#{org_id}`
|
||||
*_, org_id = old_image['id'].split('#')
|
||||
# Key pattern `MONTH#{month}#SCHEDULE#SEND_REPORT_EMAIL`
|
||||
_, month, *_ = old_image['sk'].split('#')
|
||||
|
||||
@@ -22,7 +22,6 @@ Globals:
|
||||
Function:
|
||||
CodeUri: app/
|
||||
Runtime: python3.13
|
||||
Tracing: Active
|
||||
Architectures:
|
||||
- x86_64
|
||||
Layers:
|
||||
@@ -312,7 +311,8 @@ Resources:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
Handler: events.issue_cert.lambda_handler
|
||||
# Timeout: 30
|
||||
Tracing: Active
|
||||
Timeout: 30
|
||||
LoggingConfig:
|
||||
LogGroup: !Ref EventLog
|
||||
Policies:
|
||||
@@ -391,6 +391,6 @@ Resources:
|
||||
detail:
|
||||
keys:
|
||||
id:
|
||||
- prefix: CERT#REPORTING#ORG
|
||||
- prefix: CERT_REPORTING#ORG
|
||||
sk:
|
||||
- suffix: SCHEDULE#SEND_REPORT_EMAIL
|
||||
|
||||
@@ -45,7 +45,7 @@ def test_append_cert(
|
||||
)
|
||||
|
||||
r = dynamodb_persistence_layer.collection.get_items(
|
||||
TransactKey('CERT#REPORTING#ORG#1e2eaf0e-e319-49eb-ab33-1ddec156dc94')
|
||||
TransactKey('CERT_REPORTING#ORG#1e2eaf0e-e319-49eb-ab33-1ddec156dc94')
|
||||
+ SortKey(
|
||||
sk=report_sk,
|
||||
rename_key='report_email',
|
||||
@@ -61,3 +61,31 @@ def test_append_cert(
|
||||
|
||||
assert 'course' in r['enrollment']
|
||||
assert 'ttl' in r['report_email']
|
||||
|
||||
|
||||
def test_report_exists(
|
||||
seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
event = {
|
||||
'detail': {
|
||||
'new_image': {
|
||||
'id': 'e45019d8-be7a-4a82-9b37-12a01f0127bb',
|
||||
'sk': '0',
|
||||
'course': {
|
||||
'id': '431',
|
||||
'name': 'How to Sing Better',
|
||||
},
|
||||
'cert_expires_at': '2025-07-02T00:00:00-03:06',
|
||||
'user': {
|
||||
'id': '1234',
|
||||
'name': 'Tobias Summit',
|
||||
},
|
||||
'org_id': '00237409-9384-4692-9be5-b4443a41e1c4',
|
||||
'created_at': '2025-01-01T00:00:00-03:06',
|
||||
'completed_at': '2025-01-10T00:00:00-03:06',
|
||||
}
|
||||
}
|
||||
}
|
||||
assert app.lambda_handler(event, lambda_context) # type: ignore
|
||||
|
||||
@@ -12,7 +12,7 @@ def test_send_report_email(
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
pk = 'CERT#REPORTING#ORG#00237409-9384-4692-9be5-b4443a41e1c4'
|
||||
pk = 'CERT_REPORTING#ORG#00237409-9384-4692-9be5-b4443a41e1c4'
|
||||
event = {
|
||||
'detail': {
|
||||
'old_image': {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import app.events.enroll as app
|
||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, PartitionKey
|
||||
|
||||
|
||||
def test_enroll(
|
||||
@@ -30,3 +30,6 @@ def test_enroll(
|
||||
KeyPair(enrollment_id, f'LINKED_ENTITIES#PARENT#ORDER#{order_id}'),
|
||||
)
|
||||
assert enrollment
|
||||
|
||||
r = dynamodb_persistence_layer.collection.query(PartitionKey(enrollment['id']))
|
||||
assert not any(x['sk'] == 'METADATA#DEDUPLICATION_WINDOW' for x in r['items'])
|
||||
|
||||
@@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||
|
||||
|
||||
def test_reenroll(
|
||||
def test_reenroll_custom_dedup_window(
|
||||
seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
lambda_context: LambdaContext,
|
||||
@@ -46,3 +46,8 @@ def test_reenroll(
|
||||
)
|
||||
)
|
||||
assert child
|
||||
|
||||
dedup_window = dynamodb_persistence_layer.collection.get_item(
|
||||
KeyPair(child_id, 'METADATA#DEDUPLICATION_WINDOW')
|
||||
)
|
||||
assert dedup_window
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "tenant", "org_id": "123", "name": "EDUSEG", "create_date": "2025-09-12T17:11:00.556907-03:00"}
|
||||
|
||||
// Certificate reporting
|
||||
{"id": "CERT#REPORTING#ORG#00237409-9384-4692-9be5-b4443a41e1c4", "sk": "MONTH#2025-06", "status": "PENDING"}
|
||||
{"id": "CERT#REPORTING#ORG#00237409-9384-4692-9be5-b4443a41e1c4", "sk": "MONTH#2025-07#ENROLLMENT#ba4d48e6-3671-4060-988a-d6cf97dd0ea4", "completed_at": "2025-01-10T00:00:00-03:06", "enrolled_at": "2025-01-01T00:00:00-03:06", "expires_at": "2026-02-10T20:14:42.880991", "course": {"name": "How to Sing Better", "id": "431"}, "created_at": "2025-10-11T23:39:12.194344-03:00", "user": {"name": "Tobias Summit", "id": "1234"}, "enrollment_id": "e45019d8-be7a-4a82-9b37-12a01f0127bb"}
|
||||
{"id": "CERT_REPORTING#ORG#00237409-9384-4692-9be5-b4443a41e1c4", "sk": "MONTH#2025-06", "status": "PENDING"}
|
||||
{"id": "CERT_REPORTING#ORG#00237409-9384-4692-9be5-b4443a41e1c4", "sk": "MONTH#2025-07#ENROLLMENT#ba4d48e6-3671-4060-988a-d6cf97dd0ea4", "completed_at": "2025-01-10T00:00:00-03:06", "enrolled_at": "2025-01-01T00:00:00-03:06", "expires_at": "2026-02-10T20:14:42.880991", "course": {"name": "How to Sing Better", "id": "431"}, "created_at": "2025-10-11T23:39:12.194344-03:00", "user": {"name": "Tobias Summit", "id": "1234"}, "enrollment_id": "e45019d8-be7a-4a82-9b37-12a01f0127bb"}
|
||||
|
||||
// Org
|
||||
{"id": "1e2eaf0e-e319-49eb-ab33-1ddec156dc94", "sk": "0", "name": "pytest"}
|
||||
|
||||
@@ -8,7 +8,7 @@ Globals:
|
||||
Architectures:
|
||||
- x86_64
|
||||
Layers:
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:96
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:99
|
||||
Environment:
|
||||
Variables:
|
||||
LOG_LEVEL: DEBUG
|
||||
|
||||
Reference in New Issue
Block a user