diff --git a/enrollments-events/app/config.py b/enrollments-events/app/config.py index 8ea4eeb..86a37bd 100644 --- a/enrollments-events/app/config.py +++ b/enrollments-events/app/config.py @@ -15,6 +15,9 @@ PAPERFORGE_API = 'https://paperforge.saladeaula.digital' CERT_REPORTING_URI = 's3://saladeaula.digital/certs/reporting.html' ESIGN_URI = 's3://saladeaula.digital/esigns/11de2510136adbac.pfx' +DOCSEAL_API = 'https://docs.eduseg.com.br' +DOCSEAL_KEY: str = os.getenv('DOCSEAL_KEY') # type: ignore + DBNAME: str = os.getenv('POSTGRES_DB') # type: ignore DBHOST: str = os.getenv('POSTGRES_HOST') # type: ignore diff --git a/enrollments-events/app/docseal.py b/enrollments-events/app/docseal.py new file mode 100644 index 0000000..5c80d07 --- /dev/null +++ b/enrollments-events/app/docseal.py @@ -0,0 +1,41 @@ +from typing import TypedDict + +import requests + +from config import DOCSEAL_API, DOCSEAL_KEY + +headers = { + 'X-Auth-Token': DOCSEAL_KEY, +} + +Submitter = TypedDict('Submitter', {'role': str, 'name': str, 'email': str}) +EmailMessage = TypedDict('EmailMessage', {'subject': str, 'body': str}) + + +def create_submission_from_pdf( + filename: str, + file: str, + submitters: list[Submitter], + email_message: EmailMessage, + **kwargs, +): + r = requests.post( + url=f'{DOCSEAL_API}/api/submissions/pdf', + json={ + 'name': filename, + 'documents': [ + { + 'name': filename, + 'file': file, + } + ], + 'message': email_message, + 'submitters': submitters, + **kwargs, + }, + headers=headers, + timeout=6, + ) + r.raise_for_status() + + return True diff --git a/enrollments-events/app/events/ask_to_sign.py b/enrollments-events/app/events/ask_to_sign.py new file mode 100644 index 0000000..5958178 --- /dev/null +++ b/enrollments-events/app/events/ask_to_sign.py @@ -0,0 +1,64 @@ +import base64 +from urllib.parse import urlparse + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.data_classes import ( + EventBridgeEvent, + event_source, +) +from aws_lambda_powertools.utilities.typing import LambdaContext +from layercake.dynamodb import DynamoDBPersistenceLayer +from layercake.strutils import first_word + +from boto3clients import dynamodb_client, s3_client +from config import ENROLLMENT_TABLE +from docseal import create_submission_from_pdf + +logger = Logger(__name__) +dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) + + +SUBJECT = '{first_name}, assine seu certificado agora!' +MESSAGE = """ +{first_name}, +Seu certificado já está pronto e aguardando apenas a sua assinatura digital. + +[👉 Assinar agora.]({{submitter.link}}) +""" + + +@event_source(data_class=EventBridgeEvent) +@logger.inject_lambda_context +def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: + new_image = event.detail['new_image'] + user = new_image['user'] + first_name = first_word(user['name']) + file_bytes = _get_file_bytes(new_image['cert']['s3_uri']) + file_base64 = base64.b64encode(file_bytes) + + create_submission_from_pdf( + filename=new_image['id'], + file=file_base64.decode('utf-8'), + email_message={ + 'subject': SUBJECT.format(first_name=first_name), + 'body': MESSAGE.format(first_name=first_name), + }, + submitters=[ + { + 'role': 'Aluno', + 'name': user['name'], + 'email': user['email'], + }, + ], + ) + + return True + + +def _get_file_bytes(s3_uri: str) -> bytes: + parsed = urlparse(s3_uri) + bucket = parsed.netloc + key = parsed.path.lstrip('/') + + r = s3_client.get_object(Bucket=bucket, Key=key) + return r['Body'].read() diff --git a/enrollments-events/app/events/issue_cert.py b/enrollments-events/app/events/issue_cert.py index 55458b9..e926216 100644 --- a/enrollments-events/app/events/issue_cert.py +++ b/enrollments-events/app/events/issue_cert.py @@ -17,7 +17,6 @@ from config import ( BUCKET_NAME, COURSE_TABLE, ENROLLMENT_TABLE, - ESIGN_URI, PAPERFORGE_API, ) @@ -57,7 +56,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: if cert.get('exp_interval', 0) > 0 else None ) - s3_uri = _gen_cert( + s3_uri = _generate_cert( enrollment_id, cert=cert, user=new_image['user'], @@ -112,7 +111,7 @@ User = TypedDict('User', {'name': str, 'cpf': str}) Cert = TypedDict('Cert', {'s3_uri': NotRequired[str]}) -def _gen_cert( +def _generate_cert( id: str, *, score: int | float, @@ -135,7 +134,6 @@ def _gen_cert( data=json.dumps( { 'template_uri': cert['s3_uri'], - 'sign_uri': ESIGN_URI, 'args': { 'name': user['name'], 'cpf': _cpffmt(user['cpf']), @@ -150,7 +148,7 @@ def _gen_cert( }, }, ), - timeout=5, + timeout=6, ) r.raise_for_status() diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index 4608fb8..b2d6303 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -312,7 +312,7 @@ Resources: Properties: Handler: events.issue_cert.lambda_handler Tracing: Active - Timeout: 30 + Timeout: 12 LoggingConfig: LogGroup: !Ref EventLog Policies: @@ -336,6 +336,33 @@ Resources: old_image: status: [IN_PROGRESS] + EventAskToSignFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.ask_to_sign.lambda_handler + Tracing: Active + Timeout: 12 + Policies: + - S3ReadPolicy: + BucketName: !Ref BucketName + LoggingConfig: + LogGroup: !Ref EventLog + Events: + DynamoDBEvent: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref EnrollmentTable] + detail: + keys: + sk: ["0"] + new_image: + cert: + - exists: true + old_image: + cert: + - exists: false + EventReportingAppendCertFunction: Type: AWS::Serverless::Function Properties: diff --git a/enrollments-events/tests/conftest.py b/enrollments-events/tests/conftest.py index 0dc9c06..56d327e 100644 --- a/enrollments-events/tests/conftest.py +++ b/enrollments-events/tests/conftest.py @@ -14,6 +14,7 @@ def pytest_configure(): os.environ['TZ'] = 'America/Sao_Paulo' os.environ['DYNAMODB_PARTITION_KEY'] = PK os.environ['DYNAMODB_SORT_KEY'] = SK + os.environ['DOCSEAL_KEY'] = 'gUWhWtYBgTaP8fc1q5GZ6JuUHaZzMgZna6KFBHz3Gzk' os.environ['USER_TABLE'] = PYTEST_TABLE_NAME os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME diff --git a/enrollments-events/tests/events/test_ask_to_sign.py b/enrollments-events/tests/events/test_ask_to_sign.py new file mode 100644 index 0000000..97e409b --- /dev/null +++ b/enrollments-events/tests/events/test_ask_to_sign.py @@ -0,0 +1,24 @@ +from aws_lambda_powertools.utilities.typing import LambdaContext + +import events.ask_to_sign as app + + +def test_ask_to_sign( + lambda_context: LambdaContext, +): + event = { + 'detail': { + 'new_image': { + 'id': 'e249c51b-3e68-42eb-bb4b-20659263ce1c', + 'cert': { + 's3_uri': 's3://saladeaula.digital/certs/samples/nr11-operador-de-munck.pdf' + }, + 'user': { + 'name': 'Sérgio R Siqueira', + 'email': 'sergio@somosbeta.com.br', + }, + } + } + } + + assert app.lambda_handler(event, lambda_context) # type: ignore diff --git a/enrollments-events/tests/sample.pdf b/enrollments-events/tests/sample.pdf new file mode 100644 index 0000000..37b4c92 Binary files /dev/null and b/enrollments-events/tests/sample.pdf differ diff --git a/enrollments-events/tests/test_docseal.py b/enrollments-events/tests/test_docseal.py new file mode 100644 index 0000000..2b6201c --- /dev/null +++ b/enrollments-events/tests/test_docseal.py @@ -0,0 +1,36 @@ +import base64 +import uuid +from unittest.mock import MagicMock, patch + +from docseal import create_submission_from_pdf + +SUBJECT = '{first_name}, assine seu certificado agora!' +MESSAGE = """ +{first_name}, +Seu certificado já está pronto e aguardando apenas a sua assinatura digital. + +[👉 Assinar agora.]({{submitter.link}}) +""" + + +def test_create_submission_from_pdf(): + r = MagicMock() + + with patch('docseal.requests.post', r): + with open('tests/sample.pdf', 'rb') as f: + file = base64.b64encode(f.read()) + create_submission_from_pdf( + str(uuid.uuid4()), + file=file.decode('utf-8'), + email_message={ + 'subject': SUBJECT.format(first_name='Tiago'), + 'body': MESSAGE.format(first_name='Tiago'), + }, + submitters=[ + { + 'role': 'Aluno', + 'name': 'Sérgio R Siqueira', + 'email': 'sergio@somosbeta.com.br', + }, + ], + ) diff --git a/enrollments-events/uv.lock b/enrollments-events/uv.lock index 571a7d8..506f08f 100644 --- a/enrollments-events/uv.lock +++ b/enrollments-events/uv.lock @@ -501,7 +501,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.11.0" +version = "0.11.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, diff --git a/orders-events/app/events/stopgap/remove_slots.py b/orders-events/app/events/stopgap/remove_slots.py index fe0fc33..35d87e7 100644 --- a/orders-events/app/events/stopgap/remove_slots.py +++ b/orders-events/app/events/stopgap/remove_slots.py @@ -25,7 +25,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: the total is greater than zero.""" new_image = event.detail['new_image'] data = order_layer.get_item(KeyPair(new_image['id'], '0')) - org_id = data['tenant_id'] + org_id = data.get('tenant_id') + + if not org_id: + return False policy = user_layer.collection.get_item( KeyPair(pk=org_id, sk='metadata#billing_policy'),