From 466ff824dd8cb5bd96c6ab4faf005b30b24a684a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Mon, 13 Oct 2025 14:31:29 -0300 Subject: [PATCH] renamem orders --- api.saladeaula.digital/app/app.py | 17 +++++-- api.saladeaula.digital/app/config.py | 1 + .../app/routes/courses/__init__.py | 2 +- .../app/routes/enrollments/__init__.py | 7 +++ .../app/routes/orders/__init__.py | 21 ++++++++ .../app/routes/users/__init__.py | 26 ++++++++++ .../app/routes/users/emails.py | 21 ++++++++ .../tests/routes/test_courses.py | 1 - api.saladeaula.digital/uv.lock | 10 ++-- enrollments-events/app/enrollment.py | 26 ++++++---- .../app/events/allocate_slots.py | 8 +-- .../cert_reporting/send_report_email.py | 2 + .../app/events/reenroll_if_failed.py | 5 +- .../app/events/set_cert_expired.py | 4 ++ .../stopgap/set_subscription_covered.py | 4 +- enrollments-events/template.yaml | 3 +- .../events/cert_reporting/test_append_cert.py | 1 - http-api/app/routes/enrollments/__init__.py | 3 +- .../enrollments/deduplication_window.py | 4 +- http-api/app/rules/enrollment.py | 8 +-- http-api/uv.lock | 46 ++++++++++++------ id.saladeaula.digital/app/routes/session.py | 2 + {order-events => orders-events}/Makefile | 0 .../app/boto3clients.py | 0 {order-events => orders-events}/app/config.py | 2 +- .../app/courses_export_2025-06-18_110214.db | Bin .../app/events/__init__.py | 0 .../app/events/append_org_id.py | 0 .../app/events/append_user_id.py | 0 .../app/events/billing/__init__.py | 0 .../app/events/billing/append_enrollment.py | 0 .../app/events/billing/cancel_enrollment.py | 0 .../app/events/billing/close_window.py | 0 .../events/billing/send_email_on_closing.py | 0 .../app/events/remove_slots_if_canceled.py | 0 .../app/events/stopgap/__init__.py | 0 .../app/events/stopgap/remove_slots.py | 0 .../app/events/stopgap/set_as_paid.py | 0 {order-events => orders-events}/app/utils.py | 0 .../pyproject.toml | 0 .../pyrightconfig.json | 0 .../samconfig.toml | 0 {order-events => orders-events}/template.yaml | 0 .../tests/__init__.py | 0 .../tests/conftest.py | 0 .../tests/events/__init__.py | 0 .../tests/events/billing/__init__.py | 0 .../events/billing/test_append_enrollment.py | 0 .../events/billing/test_cancel_enrollment.py | 0 .../tests/events/billing/test_close_window.py | 0 .../billing/test_send_email_on_closing.py | 0 .../tests/events/stopgap/__init__.py | 0 .../tests/events/stopgap/test_remove_slots.py | 0 .../tests/events/stopgap/test_set_as_paid.py | 0 .../tests/events/test_append_org_id.py | 0 .../tests/events/test_append_user_id.py | 0 .../events/test_remove_slots_if_canceled.py | 0 .../tests/seeds.jsonl | 0 .../tests/test_utils.py | 0 {order-events => orders-events}/uv.lock | 0 60 files changed, 165 insertions(+), 59 deletions(-) rename {order-events => orders-events}/Makefile (100%) rename {order-events => orders-events}/app/boto3clients.py (100%) rename {order-events => orders-events}/app/config.py (90%) rename {order-events => orders-events}/app/courses_export_2025-06-18_110214.db (100%) rename {order-events => orders-events}/app/events/__init__.py (100%) rename {order-events => orders-events}/app/events/append_org_id.py (100%) rename {order-events => orders-events}/app/events/append_user_id.py (100%) rename {order-events => orders-events}/app/events/billing/__init__.py (100%) rename {order-events => orders-events}/app/events/billing/append_enrollment.py (100%) rename {order-events => orders-events}/app/events/billing/cancel_enrollment.py (100%) rename {order-events => orders-events}/app/events/billing/close_window.py (100%) rename {order-events => orders-events}/app/events/billing/send_email_on_closing.py (100%) rename {order-events => orders-events}/app/events/remove_slots_if_canceled.py (100%) rename {order-events => orders-events}/app/events/stopgap/__init__.py (100%) rename {order-events => orders-events}/app/events/stopgap/remove_slots.py (100%) rename {order-events => orders-events}/app/events/stopgap/set_as_paid.py (100%) rename {order-events => orders-events}/app/utils.py (100%) rename {order-events => orders-events}/pyproject.toml (100%) rename {order-events => orders-events}/pyrightconfig.json (100%) rename {order-events => orders-events}/samconfig.toml (100%) rename {order-events => orders-events}/template.yaml (100%) rename {order-events => orders-events}/tests/__init__.py (100%) rename {order-events => orders-events}/tests/conftest.py (100%) rename {order-events => orders-events}/tests/events/__init__.py (100%) rename {order-events => orders-events}/tests/events/billing/__init__.py (100%) rename {order-events => orders-events}/tests/events/billing/test_append_enrollment.py (100%) rename {order-events => orders-events}/tests/events/billing/test_cancel_enrollment.py (100%) rename {order-events => orders-events}/tests/events/billing/test_close_window.py (100%) rename {order-events => orders-events}/tests/events/billing/test_send_email_on_closing.py (100%) rename {order-events => orders-events}/tests/events/stopgap/__init__.py (100%) rename {order-events => orders-events}/tests/events/stopgap/test_remove_slots.py (100%) rename {order-events => orders-events}/tests/events/stopgap/test_set_as_paid.py (100%) rename {order-events => orders-events}/tests/events/test_append_org_id.py (100%) rename {order-events => orders-events}/tests/events/test_append_user_id.py (100%) rename {order-events => orders-events}/tests/events/test_remove_slots_if_canceled.py (100%) rename {order-events => orders-events}/tests/seeds.jsonl (100%) rename {order-events => orders-events}/tests/test_utils.py (100%) rename {order-events => orders-events}/uv.lock (100%) diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index d9cf090..07493b0 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -14,9 +14,11 @@ from aws_lambda_powertools.utilities.typing import LambdaContext from api_gateway import JSONResponse from json_encoder import JSONEncoder -from routes import courses, enrollments +from routes import courses, enrollments, orders, users logger = Logger(__name__) +debug = 'AWS_SAM_LOCAL' in os.environ +serializer = partial(json.dumps, separators=(',', ':'), cls=JSONEncoder) cors = CORSConfig( allow_origin='*', allow_headers=['Content-Type', 'X-Requested-With', 'Authorization'], @@ -24,13 +26,18 @@ cors = CORSConfig( allow_credentials=False, ) app = APIGatewayHttpResolver( - enable_validation=True, - cors=cors, - debug='AWS_SAM_LOCAL' in os.environ, - serializer=partial(json.dumps, separators=(',', ':'), cls=JSONEncoder), + enable_validation=True, cors=cors, debug=debug, serializer=serializer ) app.include_router(courses.router, prefix='/courses') app.include_router(enrollments.router, prefix='/enrollments') +app.include_router(enrollments.cancel, prefix='/enrollments') +app.include_router(enrollments.dedup_window, prefix='/enrollments') +app.include_router(enrollments.download_cert, prefix='/enrollments') +app.include_router(enrollments.enroll, prefix='/enrollments') +app.include_router(users.router, prefix='/users') +app.include_router(users.emails, prefix='/users') +app.include_router(users.orgs, prefix='/users') +app.include_router(orders.router, prefix='/orders') @app.exception_handler(ServiceError) diff --git a/api.saladeaula.digital/app/config.py b/api.saladeaula.digital/app/config.py index fbf72d5..df027d9 100644 --- a/api.saladeaula.digital/app/config.py +++ b/api.saladeaula.digital/app/config.py @@ -1,6 +1,7 @@ import os 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 COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore diff --git a/api.saladeaula.digital/app/routes/courses/__init__.py b/api.saladeaula.digital/app/routes/courses/__init__.py index 99aea59..b6fc78c 100644 --- a/api.saladeaula.digital/app/routes/courses/__init__.py +++ b/api.saladeaula.digital/app/routes/courses/__init__.py @@ -64,7 +64,7 @@ def edit_course(course_id: str): now_ = now() if course.rawfile: - object_key = f'certs/{course_id}.html' + object_key = f'certs/templates/{course_id}.html' course.cert.s3_uri = f's3://{BUCKET_NAME}/{object_key}' s3_client.put_object( diff --git a/api.saladeaula.digital/app/routes/enrollments/__init__.py b/api.saladeaula.digital/app/routes/enrollments/__init__.py index 869c39f..382348a 100644 --- a/api.saladeaula.digital/app/routes/enrollments/__init__.py +++ b/api.saladeaula.digital/app/routes/enrollments/__init__.py @@ -6,6 +6,13 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from boto3clients import dynamodb_client from config import ENROLLMENT_TABLE +from .cancel import router as cancel +from .dedup_window import router as dedup_window +from .download_cert import router as download_cert +from .enroll import router as enroll + +__all__ = ['cancel', 'dedup_window', 'download_cert', 'enroll'] + logger = Logger(__name__) router = Router() dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) diff --git a/api.saladeaula.digital/app/routes/orders/__init__.py b/api.saladeaula.digital/app/routes/orders/__init__.py index e69de29..498433d 100644 --- a/api.saladeaula.digital/app/routes/orders/__init__.py +++ b/api.saladeaula.digital/app/routes/orders/__init__.py @@ -0,0 +1,21 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.exceptions import ( + NotFoundError, +) +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair + +from boto3clients import dynamodb_client +from config import ORDER_TABLE + +logger = Logger(__name__) +router = Router() +dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) + + +@router.get('/') +def get_order(order_id: str): + return dyn.collection.get_item( + KeyPair(order_id, '0'), + exc_cls=NotFoundError, + ) diff --git a/api.saladeaula.digital/app/routes/users/__init__.py b/api.saladeaula.digital/app/routes/users/__init__.py index e69de29..4ce0fc9 100644 --- a/api.saladeaula.digital/app/routes/users/__init__.py +++ b/api.saladeaula.digital/app/routes/users/__init__.py @@ -0,0 +1,26 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.exceptions import ( + NotFoundError, +) +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair + +from boto3clients import dynamodb_client +from config import USER_TABLE + +from .emails import router as emails +from .orgs import router as orgs + +__all__ = ['emails', 'orgs'] + +logger = Logger(__name__) +router = Router() +dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) + + +@router.get('/') +def get_user(user_id: str): + return dyn.collection.get_item( + KeyPair(user_id, '0'), + exc_cls=NotFoundError, + ) diff --git a/api.saladeaula.digital/app/routes/users/emails.py b/api.saladeaula.digital/app/routes/users/emails.py index e69de29..81cec01 100644 --- a/api.saladeaula.digital/app/routes/users/emails.py +++ b/api.saladeaula.digital/app/routes/users/emails.py @@ -0,0 +1,21 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.api_gateway import Router +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair + +from boto3clients import dynamodb_client +from config import USER_TABLE + +logger = Logger(__name__) +router = Router() +dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) + + +@router.get('//emails') +def get_emails(user_id: str): + start_key = router.current_event.get_query_string_value('start_key', None) + + return dyn.collection.query( + # Post-migration (users): rename `emails` to `EMAIL` + key=KeyPair(user_id, 'emails'), + start_key=start_key, + ) diff --git a/api.saladeaula.digital/tests/routes/test_courses.py b/api.saladeaula.digital/tests/routes/test_courses.py index 1960eae..03d36b6 100644 --- a/api.saladeaula.digital/tests/routes/test_courses.py +++ b/api.saladeaula.digital/tests/routes/test_courses.py @@ -82,4 +82,3 @@ def test_sample( lambda_context, ) assert r['statusCode'] == HTTPStatus.OK - # print(r['body']) diff --git a/api.saladeaula.digital/uv.lock b/api.saladeaula.digital/uv.lock index 783e7e8..5fad3d1 100644 --- a/api.saladeaula.digital/uv.lock +++ b/api.saladeaula.digital/uv.lock @@ -66,14 +66,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.4" +version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/bb/73a1f1c64ee527877f64122422dafe5b87a846ccf4ac933fe21bcbb8fee8/authlib-1.6.4.tar.gz", hash = "sha256:104b0442a43061dc8bc23b133d1d06a2b0a9c2e3e33f34c4338929e816287649", size = 164046, upload-time = "2025-09-17T09:59:23.897Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/aa/91355b5f539caf1b94f0e66ff1e4ee39373b757fce08204981f7829ede51/authlib-1.6.4-py2.py3-none-any.whl", hash = "sha256:39313d2a2caac3ecf6d8f95fbebdfd30ae6ea6ae6a6db794d976405fdd9aa796", size = 243076, upload-time = "2025-09-17T09:59:22.259Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, ] [[package]] @@ -592,7 +592,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.10.0" +version = "0.10.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, @@ -620,7 +620,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "arnparse", specifier = ">=0.0.2" }, - { name = "authlib", specifier = ">=1.6.1" }, + { name = "authlib", specifier = ">=1.6.5" }, { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" }, { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, diff --git a/enrollments-events/app/enrollment.py b/enrollments-events/app/enrollment.py index 9a9c016..856e273 100644 --- a/enrollments-events/app/enrollment.py +++ b/enrollments-events/app/enrollment.py @@ -7,8 +7,21 @@ from layercake.strutils import md5_hash from schemas import Enrollment -Org = TypedDict('Org', {'org_id': str, 'name': str}) -DeduplicationWindow = TypedDict('DeduplicationWindow', {'offset_days': int}) +Org = TypedDict( + 'Org', + { + 'org_id': str, + 'name': str, + }, +) + +DeduplicationWindow = TypedDict( + 'DeduplicationWindow', + { + 'offset_days': int, + }, +) + Subscription = TypedDict( 'Subscription', { @@ -44,7 +57,6 @@ def enroll( deduplication_window: DeduplicationWindow | None = None, persistence_layer: DynamoDBPersistenceLayer, ) -> bool: - """Enrolls a user into a course and schedules lifecycle events.""" now_ = now() user = enrollment.user course = enrollment.course @@ -60,9 +72,7 @@ def enroll( **enrollment.model_dump(), } | ({'subscription_covered': True} if subscription else {}) - | ({'tenant_id': org['org_id']} if org else {}), - # Post-migration: uncomment the following line - # | ({'org_id': org['org_id']} if org else {}), + | ({'org_id': org['org_id']} if org else {}), ) # Relationships between this enrollment and its related entities @@ -97,9 +107,7 @@ def enroll( transact.put( item={ 'id': enrollment.id, - # Post-migration: uncomment the following line - # 'sk': 'ORG', - 'sk': 'tenant', + 'sk': 'ORG', 'created_at': now_, } | org diff --git a/enrollments-events/app/events/allocate_slots.py b/enrollments-events/app/events/allocate_slots.py index b17b88e..b36c9f5 100644 --- a/enrollments-events/app/events/allocate_slots.py +++ b/enrollments-events/app/events/allocate_slots.py @@ -34,7 +34,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: order = order_layer.collection.get_items( TransactKey(order_id) + SortKey('0') + SortKey('items', path_spec='items'), ) - # Post-migration: rename `tenant_id` to `org_id` + # Post-migration (orders): rename `tenant_id` to `org_id` org_id = order['tenant_id'] items = { item['id']: int(item['quantity']) @@ -54,7 +54,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: item={ 'id': f'vacancies#{org_id}', 'sk': f'{order_id}#{uuid4()}', - # Post-migration: uncomment the follow lines + # Post-migration (orders): uncomment the follow lines # 'id': f'SLOT#ORG#{org_id}', # 'sk': f'ORDER#{order_id}#ENROLLMENT#{uuid4()}', 'course': asdict(slot), @@ -81,8 +81,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: class Course: id: str name: str - # Post-migration: remove the following line - time_in_days: int def _get_courses(ids: set) -> tuple[Course, ...]: @@ -95,8 +93,6 @@ def _get_courses(ids: set) -> tuple[Course, ...]: Course( id=idx, name=obj['name'], - # Post-migration: remove the following line - time_in_days=int(obj['access_period']), ) for idx, obj in result.items() ) diff --git a/enrollments-events/app/events/cert_reporting/send_report_email.py b/enrollments-events/app/events/cert_reporting/send_report_email.py index c2d1bce..6e680ee 100644 --- a/enrollments-events/app/events/cert_reporting/send_report_email.py +++ b/enrollments-events/app/events/cert_reporting/send_report_email.py @@ -81,6 +81,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: emailmsg = Message( from_=EMAIL_SENDER, to=_get_admin_emails(org_id), + reply_to=REPLY_TO, + bcc=BCC, subject=SUBJECT.format(month=month), ) emailmsg.add_alternative(MESSAGE.format(month=month)) diff --git a/enrollments-events/app/events/reenroll_if_failed.py b/enrollments-events/app/events/reenroll_if_failed.py index 072dcb9..c71bee7 100644 --- a/enrollments-events/app/events/reenroll_if_failed.py +++ b/enrollments-events/app/events/reenroll_if_failed.py @@ -29,11 +29,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: path_spec='offset_days', rename_key='dedup_window_offset_days', ) - + SortKey('konviva') - + SortKey('tenant', rename_key='org'), + + SortKey('ORG', rename_key='org') + + SortKey('konviva'), # Post-migration: uncomment the following lines # + SortKey('KONVIVA', rename_key='konviva') - # + SortKey('ORG', rename_key='org'), flatten_top=False, ) user = User.model_validate(new_image['user']) diff --git a/enrollments-events/app/events/set_cert_expired.py b/enrollments-events/app/events/set_cert_expired.py index 09bd280..a199208 100644 --- a/enrollments-events/app/events/set_cert_expired.py +++ b/enrollments-events/app/events/set_cert_expired.py @@ -45,6 +45,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ':completed': 'COMPLETED', ':now': now_, }, + exc_cls=StatusConflictError, ) transact.put( item={ @@ -68,3 +69,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: return False else: return True + + +class StatusConflictError(Exception): ... diff --git a/enrollments-events/app/events/stopgap/set_subscription_covered.py b/enrollments-events/app/events/stopgap/set_subscription_covered.py index 7a4a4dd..54f84e7 100644 --- a/enrollments-events/app/events/stopgap/set_subscription_covered.py +++ b/enrollments-events/app/events/stopgap/set_subscription_covered.py @@ -24,7 +24,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] now_ = now() terms = user_layer.get_item( - # Post-migration: uncomment the following line + # Post-migration (users): uncomment the following line # KeyPair(new_image['org_id'], 'METADATA#BILLING_TERMS'), KeyPair(new_image['tenant_id'], 'metadata#billing_policy'), ) @@ -48,7 +48,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: item={ 'id': new_image['id'], 'sk': 'METADATA#SUBSCRIPTION_COVERED', - 'org_id': new_image['tenant_id'], + 'org_id': new_image['org_id'], 'billing_day': terms['billing_day'], 'created_at': now_, }, diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index 9767581..a3c501d 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -72,8 +72,7 @@ Resources: detail: new_image: sk: ["0"] - # Post-migration: rename `tenant_id` to `org_id` - tenant_id: + org_id: - exists: true EventPatchCourseMetadataFunction: diff --git a/enrollments-events/tests/events/cert_reporting/test_append_cert.py b/enrollments-events/tests/events/cert_reporting/test_append_cert.py index b1bd03b..a629852 100644 --- a/enrollments-events/tests/events/cert_reporting/test_append_cert.py +++ b/enrollments-events/tests/events/cert_reporting/test_append_cert.py @@ -26,7 +26,6 @@ def test_append_cert( 'name': 'How to Sing Better', }, 'cert': { - # 'expires_at': '2026-02-10T20:14:42.880991', 'expires_at': expires_at.isoformat(), }, 'user': { diff --git a/http-api/app/routes/enrollments/__init__.py b/http-api/app/routes/enrollments/__init__.py index 88005c3..866b2df 100644 --- a/http-api/app/routes/enrollments/__init__.py +++ b/http-api/app/routes/enrollments/__init__.py @@ -49,8 +49,9 @@ def get_enrollments(): if tenant.id != '*': filter_ = [ { - 'attr': 'tenant_id', + 'attr': 'org_id', 'op': '=', + # Post-migration (enrollemnt): rename `tenant` to `org` 'value': tenant.id, }, ] + filter_ diff --git a/http-api/app/routes/enrollments/deduplication_window.py b/http-api/app/routes/enrollments/deduplication_window.py index 15008dd..7327036 100644 --- a/http-api/app/routes/enrollments/deduplication_window.py +++ b/http-api/app/routes/enrollments/deduplication_window.py @@ -23,7 +23,7 @@ class DeduplicationWindow(BaseModel): ) def deduplication_window(id: str, payload: DeduplicationWindow): with enrollment_layer.transact_writer() as transact: - transact.delete(key=KeyPair(id, 'lock')) - transact.delete(key=KeyPair('lock', payload.lock_hash)) + transact.delete(key=KeyPair(id, 'LOCK')) + transact.delete(key=KeyPair('LOCK', payload.lock_hash)) return payload diff --git a/http-api/app/rules/enrollment.py b/http-api/app/rules/enrollment.py index ebae905..98994ec 100644 --- a/http-api/app/rules/enrollment.py +++ b/http-api/app/rules/enrollment.py @@ -69,7 +69,7 @@ def enroll( now_ = now() user = enrollment.user course = enrollment.course - tenant_id = tenant['id'] + org_id = tenant['id'] lock_hash = md5_hash('%s%s' % (user.id, course.id)) with persistence_layer.transact_writer() as transact: @@ -80,15 +80,15 @@ def enroll( item={ 'sk': '0', 'created_at': now_, - 'tenant_id': tenant_id, + 'org_id': org_id, **enrollment.model_dump(), }, ) transact.put( item={ 'id': enrollment.id, - 'sk': 'tenant', - 'org_id': tenant_id, + 'sk': 'ORG', + 'org_id': org_id, 'name': tenant['name'], 'created_at': now_, }, diff --git a/http-api/uv.lock b/http-api/uv.lock index 0a2987d..4a085d0 100644 --- a/http-api/uv.lock +++ b/http-api/uv.lock @@ -31,14 +31,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.1" +version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, ] [[package]] @@ -483,6 +483,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "joserfc" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/a0/4b8dfecc8ec3c15aa1f2ff7d5b947344378b5b595ce37c8a8fe6e25c1400/joserfc-1.4.0.tar.gz", hash = "sha256:e8c2f327bf10a937d284d57e9f8aec385381e5e5850469b50a7dade1aba59759", size = 196339, upload-time = "2025-10-09T07:47:00.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/05/342459b7629c6fcb5f99a646886ee2904491955b8cce6b26b0b9a498f67c/joserfc-1.4.0-py3-none-any.whl", hash = "sha256:46917e6b53f1ec0c7e20d34d6f3e6c27da0fa43d0d4ebfb89aada7c86582933a", size = 66390, upload-time = "2025-10-09T07:46:59.591Z" }, +] + [[package]] name = "jsonlines" version = "4.0.0" @@ -509,7 +521,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.14" +version = "0.10.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, @@ -518,6 +530,7 @@ dependencies = [ { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, + { name = "joserfc" }, { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, @@ -525,7 +538,7 @@ dependencies = [ { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, - { name = "pyjwt" }, + { name = "python-multipart" }, { name = "pytz" }, { name = "requests" }, { name = "smart-open", extra = ["s3"] }, @@ -536,11 +549,12 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "arnparse", specifier = ">=0.0.2" }, - { name = "authlib", specifier = ">=1.6.1" }, + { name = "authlib", specifier = ">=1.6.5" }, { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" }, { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, { name = "glom", specifier = ">=24.11.0" }, + { name = "joserfc", specifier = ">=1.2.2" }, { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, @@ -548,7 +562,7 @@ requires-dist = [ { name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, - { name = "pyjwt", specifier = ">=2.10.1" }, + { name = "python-multipart", specifier = ">=0.0.20" }, { name = "pytz", specifier = ">=2025.1" }, { name = "requests", specifier = ">=2.32.3" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, @@ -865,15 +879,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" }, ] -[[package]] -name = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, -] - [[package]] name = "pytest" version = "8.3.5" @@ -923,6 +928,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" }, ] +[[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]] name = "pytz" version = "2025.2" diff --git a/id.saladeaula.digital/app/routes/session.py b/id.saladeaula.digital/app/routes/session.py index f512185..5e1da0f 100644 --- a/id.saladeaula.digital/app/routes/session.py +++ b/id.saladeaula.digital/app/routes/session.py @@ -90,6 +90,8 @@ def _get_idp_user( import hashlib import hmac + # That should be removed when completing the migration + # to our own OAuth2 implementation. client_id = '3ijacqc7r2jc9l4oli2b41f7te' client_secret = 'amktf9l40g1mlqdo9fjlcfvpn2cp3mvh4pt97hu55sfelccos58' diff --git a/order-events/Makefile b/orders-events/Makefile similarity index 100% rename from order-events/Makefile rename to orders-events/Makefile diff --git a/order-events/app/boto3clients.py b/orders-events/app/boto3clients.py similarity index 100% rename from order-events/app/boto3clients.py rename to orders-events/app/boto3clients.py diff --git a/order-events/app/config.py b/orders-events/app/config.py similarity index 90% rename from order-events/app/config.py rename to orders-events/app/config.py index d84b1ed..4401c2f 100644 --- a/order-events/app/config.py +++ b/orders-events/app/config.py @@ -18,4 +18,4 @@ else: SQLITE_TABLE = 'courses' PAPERFORGE_API = 'https://paperforge.saladeaula.digital' -TEMPLATE_URI = 's3://saladeaula.digital/billing/template.html' +BILLING_TEMPLATE_URI = 's3://saladeaula.digital/billing/template.html' diff --git a/order-events/app/courses_export_2025-06-18_110214.db b/orders-events/app/courses_export_2025-06-18_110214.db similarity index 100% rename from order-events/app/courses_export_2025-06-18_110214.db rename to orders-events/app/courses_export_2025-06-18_110214.db diff --git a/order-events/app/events/__init__.py b/orders-events/app/events/__init__.py similarity index 100% rename from order-events/app/events/__init__.py rename to orders-events/app/events/__init__.py diff --git a/order-events/app/events/append_org_id.py b/orders-events/app/events/append_org_id.py similarity index 100% rename from order-events/app/events/append_org_id.py rename to orders-events/app/events/append_org_id.py diff --git a/order-events/app/events/append_user_id.py b/orders-events/app/events/append_user_id.py similarity index 100% rename from order-events/app/events/append_user_id.py rename to orders-events/app/events/append_user_id.py diff --git a/order-events/app/events/billing/__init__.py b/orders-events/app/events/billing/__init__.py similarity index 100% rename from order-events/app/events/billing/__init__.py rename to orders-events/app/events/billing/__init__.py diff --git a/order-events/app/events/billing/append_enrollment.py b/orders-events/app/events/billing/append_enrollment.py similarity index 100% rename from order-events/app/events/billing/append_enrollment.py rename to orders-events/app/events/billing/append_enrollment.py diff --git a/order-events/app/events/billing/cancel_enrollment.py b/orders-events/app/events/billing/cancel_enrollment.py similarity index 100% rename from order-events/app/events/billing/cancel_enrollment.py rename to orders-events/app/events/billing/cancel_enrollment.py diff --git a/order-events/app/events/billing/close_window.py b/orders-events/app/events/billing/close_window.py similarity index 100% rename from order-events/app/events/billing/close_window.py rename to orders-events/app/events/billing/close_window.py diff --git a/order-events/app/events/billing/send_email_on_closing.py b/orders-events/app/events/billing/send_email_on_closing.py similarity index 100% rename from order-events/app/events/billing/send_email_on_closing.py rename to orders-events/app/events/billing/send_email_on_closing.py diff --git a/order-events/app/events/remove_slots_if_canceled.py b/orders-events/app/events/remove_slots_if_canceled.py similarity index 100% rename from order-events/app/events/remove_slots_if_canceled.py rename to orders-events/app/events/remove_slots_if_canceled.py diff --git a/order-events/app/events/stopgap/__init__.py b/orders-events/app/events/stopgap/__init__.py similarity index 100% rename from order-events/app/events/stopgap/__init__.py rename to orders-events/app/events/stopgap/__init__.py diff --git a/order-events/app/events/stopgap/remove_slots.py b/orders-events/app/events/stopgap/remove_slots.py similarity index 100% rename from order-events/app/events/stopgap/remove_slots.py rename to orders-events/app/events/stopgap/remove_slots.py diff --git a/order-events/app/events/stopgap/set_as_paid.py b/orders-events/app/events/stopgap/set_as_paid.py similarity index 100% rename from order-events/app/events/stopgap/set_as_paid.py rename to orders-events/app/events/stopgap/set_as_paid.py diff --git a/order-events/app/utils.py b/orders-events/app/utils.py similarity index 100% rename from order-events/app/utils.py rename to orders-events/app/utils.py diff --git a/order-events/pyproject.toml b/orders-events/pyproject.toml similarity index 100% rename from order-events/pyproject.toml rename to orders-events/pyproject.toml diff --git a/order-events/pyrightconfig.json b/orders-events/pyrightconfig.json similarity index 100% rename from order-events/pyrightconfig.json rename to orders-events/pyrightconfig.json diff --git a/order-events/samconfig.toml b/orders-events/samconfig.toml similarity index 100% rename from order-events/samconfig.toml rename to orders-events/samconfig.toml diff --git a/order-events/template.yaml b/orders-events/template.yaml similarity index 100% rename from order-events/template.yaml rename to orders-events/template.yaml diff --git a/order-events/tests/__init__.py b/orders-events/tests/__init__.py similarity index 100% rename from order-events/tests/__init__.py rename to orders-events/tests/__init__.py diff --git a/order-events/tests/conftest.py b/orders-events/tests/conftest.py similarity index 100% rename from order-events/tests/conftest.py rename to orders-events/tests/conftest.py diff --git a/order-events/tests/events/__init__.py b/orders-events/tests/events/__init__.py similarity index 100% rename from order-events/tests/events/__init__.py rename to orders-events/tests/events/__init__.py diff --git a/order-events/tests/events/billing/__init__.py b/orders-events/tests/events/billing/__init__.py similarity index 100% rename from order-events/tests/events/billing/__init__.py rename to orders-events/tests/events/billing/__init__.py diff --git a/order-events/tests/events/billing/test_append_enrollment.py b/orders-events/tests/events/billing/test_append_enrollment.py similarity index 100% rename from order-events/tests/events/billing/test_append_enrollment.py rename to orders-events/tests/events/billing/test_append_enrollment.py diff --git a/order-events/tests/events/billing/test_cancel_enrollment.py b/orders-events/tests/events/billing/test_cancel_enrollment.py similarity index 100% rename from order-events/tests/events/billing/test_cancel_enrollment.py rename to orders-events/tests/events/billing/test_cancel_enrollment.py diff --git a/order-events/tests/events/billing/test_close_window.py b/orders-events/tests/events/billing/test_close_window.py similarity index 100% rename from order-events/tests/events/billing/test_close_window.py rename to orders-events/tests/events/billing/test_close_window.py diff --git a/order-events/tests/events/billing/test_send_email_on_closing.py b/orders-events/tests/events/billing/test_send_email_on_closing.py similarity index 100% rename from order-events/tests/events/billing/test_send_email_on_closing.py rename to orders-events/tests/events/billing/test_send_email_on_closing.py diff --git a/order-events/tests/events/stopgap/__init__.py b/orders-events/tests/events/stopgap/__init__.py similarity index 100% rename from order-events/tests/events/stopgap/__init__.py rename to orders-events/tests/events/stopgap/__init__.py diff --git a/order-events/tests/events/stopgap/test_remove_slots.py b/orders-events/tests/events/stopgap/test_remove_slots.py similarity index 100% rename from order-events/tests/events/stopgap/test_remove_slots.py rename to orders-events/tests/events/stopgap/test_remove_slots.py diff --git a/order-events/tests/events/stopgap/test_set_as_paid.py b/orders-events/tests/events/stopgap/test_set_as_paid.py similarity index 100% rename from order-events/tests/events/stopgap/test_set_as_paid.py rename to orders-events/tests/events/stopgap/test_set_as_paid.py diff --git a/order-events/tests/events/test_append_org_id.py b/orders-events/tests/events/test_append_org_id.py similarity index 100% rename from order-events/tests/events/test_append_org_id.py rename to orders-events/tests/events/test_append_org_id.py diff --git a/order-events/tests/events/test_append_user_id.py b/orders-events/tests/events/test_append_user_id.py similarity index 100% rename from order-events/tests/events/test_append_user_id.py rename to orders-events/tests/events/test_append_user_id.py diff --git a/order-events/tests/events/test_remove_slots_if_canceled.py b/orders-events/tests/events/test_remove_slots_if_canceled.py similarity index 100% rename from order-events/tests/events/test_remove_slots_if_canceled.py rename to orders-events/tests/events/test_remove_slots_if_canceled.py diff --git a/order-events/tests/seeds.jsonl b/orders-events/tests/seeds.jsonl similarity index 100% rename from order-events/tests/seeds.jsonl rename to orders-events/tests/seeds.jsonl diff --git a/order-events/tests/test_utils.py b/orders-events/tests/test_utils.py similarity index 100% rename from order-events/tests/test_utils.py rename to orders-events/tests/test_utils.py diff --git a/order-events/uv.lock b/orders-events/uv.lock similarity index 100% rename from order-events/uv.lock rename to orders-events/uv.lock