diff --git a/README.md b/README.md index a16ddd4..6ea0696 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ Quando o responsável é uma pessoa física (CPF). ### Vagas ```json -{"id": "slots#org#100", "sk": "order#101#faa8a547-bb9b-4103-bd8c-8fbe96b4056f", "course": {"name": "pytest"}} -{"id": "slots#org#100", "sk": "order#101#afffbdde-fe58-4df7-b4d5-7553a571d32a", "course": {"name": "pytest"}} +{"id": "slots#org#100", "sk": "order#101#enrollment#faa8a547-bb9b-4103-bd8c-8fbe96b4056f", "course": {"name": "pytest"}} +{"id": "slots#org#100", "sk": "order#101#enrollment#afffbdde-fe58-4df7-b4d5-7553a571d32a", "course": {"name": "pytest"}} ``` ### Emails/eventos agendados @@ -94,7 +94,7 @@ Se houver `metadata#parent_slot`, deve ser devolvido. ```json {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "0"} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "cancel_policy"} -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "metadata#parent_slot", "slot": {"id": "slots#org#123", "sk": "order#1221#f7120daf-96d2-4639-b8f4-d736fd99e4ee"}} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "metadata#parent_slot", "slot": {"id": "slots#org#123", "sk": "order#1221#enrollment#9omWNKymwU5U4aeun6mWzZ"}} ``` # Cursos diff --git a/http-api/app/routes/courses/__init__.py b/http-api/app/routes/courses/__init__.py index 7eda42a..b1bc7f4 100644 --- a/http-api/app/routes/courses/__init__.py +++ b/http-api/app/routes/courses/__init__.py @@ -32,7 +32,7 @@ user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) def get_courses(): event = router.current_event query = event.get_query_string_value('q', '') - sort = event.get_query_string_value('sort', 'create_date:desc') + sort = event.get_query_string_value('sort', 'created_at:desc') page = int(event.get_query_string_value('page', '1')) hits_per_page = int(event.get_query_string_value('hitsPerPage', '25')) @@ -58,11 +58,13 @@ def get_courses(): ) def post_course(payload: Course): tenant: Tenant = router.context['tenant'] + create_course( payload, - Org(id=tenant.id, name=tenant.name), + tenant_id=str(tenant.id), persistence_layer=course_layer, ) + return JSONResponse( body=payload, status_code=HTTPStatus.CREATED, diff --git a/http-api/app/routes/users/__init__.py b/http-api/app/routes/users/__init__.py index 04e5215..b8d11fc 100644 --- a/http-api/app/routes/users/__init__.py +++ b/http-api/app/routes/users/__init__.py @@ -129,7 +129,7 @@ def put_user(id: str, payload: UserData): @router.get('/', compress=True, tags=['User'], summary='Get user') def get_user(id: str): return user_collect.get_items( - TransactKey(id) + SortKey('0') + SortKey('last_profile_edit') + TransactKey(id) + SortKey('0') + SortKey('rate_limit#user_update') ) diff --git a/http-api/app/rules/course.py b/http-api/app/rules/course.py index 80088cf..0e1c35d 100644 --- a/http-api/app/rules/course.py +++ b/http-api/app/rules/course.py @@ -1,37 +1,24 @@ from layercake.dateutils import now from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair -from models import Course, Org +from models import Course def create_course( course: Course, - org: Org, /, + tenant_id: str, persistence_layer: DynamoDBPersistenceLayer, ): now_ = now() - - with persistence_layer.transact_writer() as transact: - transact.put( - item={ - 'sk': '0', - 'tenant_id': {org.id}, - 'created_at': now_, - **course.model_dump(), - } - ) - transact.put( - item={ - 'id': course.id, - 'sk': 'metadata#tenant', - 'org_id': org.id, - 'name': org.name, - 'created_at': now_, - } - ) - - return True + return persistence_layer.put_item( + item={ + 'sk': '0', + 'tenant_id': tenant_id, + 'created_at': now_, + **course.model_dump(), + } + ) def update_course( diff --git a/konviva-events/app/events/cancel.py b/konviva-events/app/events/cancel.py new file mode 100644 index 0000000..dd97a5c --- /dev/null +++ b/konviva-events/app/events/cancel.py @@ -0,0 +1,30 @@ +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, KeyPair + +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) +@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')) + + try: + result = konviva.cancel_enrollment(data['enrollment_id']) + except Exception as exc: + logger.exception(exc) + return False + else: + logger.info('Enrollment canceled', result=result) + return True diff --git a/konviva-events/app/konviva.py b/konviva-events/app/konviva.py index 510d818..e8c5fca 100644 --- a/konviva-events/app/konviva.py +++ b/konviva-events/app/konviva.py @@ -31,7 +31,7 @@ def create_user( name: str, email: str, cpf: str | None, -) -> dict: +) -> int: url = urlparse(KONVIVA_API_URL)._replace( path='/action/api/integrarUsuario', query='sendMail=false', @@ -58,9 +58,10 @@ def create_user( }, ) r.raise_for_status() + data = r.json() # Because Konviva does not return the proper HTTP status code - if err := glom(r.json(), 'errors', default=None): + if err := glom(data, 'errors', default=None): err = err[0] if isinstance(err, list) else err if err == 'Login já existente': @@ -68,7 +69,7 @@ def create_user( else: raise KonvivaError(err) - return r.json().get('IDUsuario') + return int(data.get('IDUsuario')) def update_user(id: str, **kwargs) -> dict: @@ -86,12 +87,13 @@ def update_user(id: str, **kwargs) -> dict: }, ) r.raise_for_status() + data = r.json() - if err := glom(r.json(), 'errors', default=None): + if err := glom(data, 'errors', default=None): err = err[0] if isinstance(err, list) else err raise KonvivaError(err) - return r.json() + return data def get_users_by_email(email: str) -> list[dict]: @@ -104,29 +106,48 @@ def get_users_by_email(email: str) -> list[dict]: headers=headers, ) r.raise_for_status() + data = r.json() - if glom(r.json(), '0.errors', default=None): + if glom(data, '0.errors', default=None): return [] - return r.json() + return data -def enroll(user_id: str, class_id: str) -> str: +def _post_enrollment(json: dict) -> dict: url = urlparse(KONVIVA_API_URL)._replace(path='/action/api/integrarMatricula') r = requests.post( url=url.geturl(), headers=headers, - json={ - 'IDUsuario': str(user_id), - 'IDTurma': str(class_id), - 'StatusMatricula': 'MATRICULADO', - }, + json=json, ) r.raise_for_status() + data = r.json() - if err := glom(r.json(), 'errors', default=None): + if err := glom(data, 'errors', default=None): err = err[0] if isinstance(err, list) else err raise KonvivaError(err) - return r.json().get('IDMatricula') + return data + + +def enroll(user_id: str, class_id: str) -> int: + r = _post_enrollment( + { + 'IDUsuario': int(user_id), + 'IDTurma': int(class_id), + 'StatusMatricula': 'MATRICULADO', + } + ) + + return int(r.get('IDMatricula')) # type: ignore + + +def cancel_enrollment(id: str) -> dict: + return _post_enrollment( + { + 'IDMatricula': str(id), + 'StatusMatricula': 'CANCELADO', + } + ) diff --git a/konviva-events/template.yaml b/konviva-events/template.yaml index ddee80b..1908639 100644 --- a/konviva-events/template.yaml +++ b/konviva-events/template.yaml @@ -81,3 +81,26 @@ Resources: sk: ["konviva"] enrollment_id: - exists: false + + EventCancelFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.cancel.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBReadPolicy: + TableName: !Ref EnrollmentTable + Events: + DynamoDBEvent: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref EnrollmentTable] + detail-type: [MODIFY] + detail: + new_image: + sk: ["0"] + status: [CANCELED] + old_image: + status: [PENDING]