renamem orders
This commit is contained in:
@@ -14,9 +14,11 @@ from aws_lambda_powertools.utilities.typing import LambdaContext
|
|||||||
|
|
||||||
from api_gateway import JSONResponse
|
from api_gateway import JSONResponse
|
||||||
from json_encoder import JSONEncoder
|
from json_encoder import JSONEncoder
|
||||||
from routes import courses, enrollments
|
from routes import courses, enrollments, orders, users
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
|
debug = 'AWS_SAM_LOCAL' in os.environ
|
||||||
|
serializer = partial(json.dumps, separators=(',', ':'), cls=JSONEncoder)
|
||||||
cors = CORSConfig(
|
cors = CORSConfig(
|
||||||
allow_origin='*',
|
allow_origin='*',
|
||||||
allow_headers=['Content-Type', 'X-Requested-With', 'Authorization'],
|
allow_headers=['Content-Type', 'X-Requested-With', 'Authorization'],
|
||||||
@@ -24,13 +26,18 @@ cors = CORSConfig(
|
|||||||
allow_credentials=False,
|
allow_credentials=False,
|
||||||
)
|
)
|
||||||
app = APIGatewayHttpResolver(
|
app = APIGatewayHttpResolver(
|
||||||
enable_validation=True,
|
enable_validation=True, cors=cors, debug=debug, serializer=serializer
|
||||||
cors=cors,
|
|
||||||
debug='AWS_SAM_LOCAL' in os.environ,
|
|
||||||
serializer=partial(json.dumps, separators=(',', ':'), cls=JSONEncoder),
|
|
||||||
)
|
)
|
||||||
app.include_router(courses.router, prefix='/courses')
|
app.include_router(courses.router, prefix='/courses')
|
||||||
app.include_router(enrollments.router, prefix='/enrollments')
|
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)
|
@app.exception_handler(ServiceError)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
|
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
|
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
||||||
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
|
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ def edit_course(course_id: str):
|
|||||||
now_ = now()
|
now_ = now()
|
||||||
|
|
||||||
if course.rawfile:
|
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}'
|
course.cert.s3_uri = f's3://{BUCKET_NAME}/{object_key}'
|
||||||
|
|
||||||
s3_client.put_object(
|
s3_client.put_object(
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
|||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import ENROLLMENT_TABLE
|
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__)
|
logger = Logger(__name__)
|
||||||
router = Router()
|
router = Router()
|
||||||
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
|
|||||||
@@ -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('/<order_id>')
|
||||||
|
def get_order(order_id: str):
|
||||||
|
return dyn.collection.get_item(
|
||||||
|
KeyPair(order_id, '0'),
|
||||||
|
exc_cls=NotFoundError,
|
||||||
|
)
|
||||||
|
|||||||
@@ -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('/<user_id>')
|
||||||
|
def get_user(user_id: str):
|
||||||
|
return dyn.collection.get_item(
|
||||||
|
KeyPair(user_id, '0'),
|
||||||
|
exc_cls=NotFoundError,
|
||||||
|
)
|
||||||
|
|||||||
@@ -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('/<user_id>/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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -82,4 +82,3 @@ def test_sample(
|
|||||||
lambda_context,
|
lambda_context,
|
||||||
)
|
)
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
# print(r['body'])
|
|
||||||
|
|||||||
10
api.saladeaula.digital/uv.lock
generated
10
api.saladeaula.digital/uv.lock
generated
@@ -66,14 +66,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authlib"
|
name = "authlib"
|
||||||
version = "1.6.4"
|
version = "1.6.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cryptography" },
|
{ 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 = [
|
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]]
|
[[package]]
|
||||||
@@ -592,7 +592,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.10.0"
|
version = "0.10.1"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
@@ -620,7 +620,7 @@ dependencies = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "arnparse", specifier = ">=0.0.2" },
|
{ 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 = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
|
||||||
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
||||||
{ name = "ftfy", specifier = ">=6.3.1" },
|
{ name = "ftfy", specifier = ">=6.3.1" },
|
||||||
|
|||||||
@@ -7,8 +7,21 @@ from layercake.strutils import md5_hash
|
|||||||
|
|
||||||
from schemas import Enrollment
|
from schemas import Enrollment
|
||||||
|
|
||||||
Org = TypedDict('Org', {'org_id': str, 'name': str})
|
Org = TypedDict(
|
||||||
DeduplicationWindow = TypedDict('DeduplicationWindow', {'offset_days': int})
|
'Org',
|
||||||
|
{
|
||||||
|
'org_id': str,
|
||||||
|
'name': str,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
DeduplicationWindow = TypedDict(
|
||||||
|
'DeduplicationWindow',
|
||||||
|
{
|
||||||
|
'offset_days': int,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Subscription = TypedDict(
|
Subscription = TypedDict(
|
||||||
'Subscription',
|
'Subscription',
|
||||||
{
|
{
|
||||||
@@ -44,7 +57,6 @@ def enroll(
|
|||||||
deduplication_window: DeduplicationWindow | None = None,
|
deduplication_window: DeduplicationWindow | None = None,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Enrolls a user into a course and schedules lifecycle events."""
|
|
||||||
now_ = now()
|
now_ = now()
|
||||||
user = enrollment.user
|
user = enrollment.user
|
||||||
course = enrollment.course
|
course = enrollment.course
|
||||||
@@ -60,9 +72,7 @@ def enroll(
|
|||||||
**enrollment.model_dump(),
|
**enrollment.model_dump(),
|
||||||
}
|
}
|
||||||
| ({'subscription_covered': True} if subscription else {})
|
| ({'subscription_covered': True} if subscription else {})
|
||||||
| ({'tenant_id': org['org_id']} if org else {}),
|
| ({'org_id': org['org_id']} if org else {}),
|
||||||
# Post-migration: uncomment the following line
|
|
||||||
# | ({'org_id': org['org_id']} if org else {}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationships between this enrollment and its related entities
|
# Relationships between this enrollment and its related entities
|
||||||
@@ -97,9 +107,7 @@ def enroll(
|
|||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': enrollment.id,
|
'id': enrollment.id,
|
||||||
# Post-migration: uncomment the following line
|
'sk': 'ORG',
|
||||||
# 'sk': 'ORG',
|
|
||||||
'sk': 'tenant',
|
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
}
|
}
|
||||||
| org
|
| org
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
order = order_layer.collection.get_items(
|
order = order_layer.collection.get_items(
|
||||||
TransactKey(order_id) + SortKey('0') + SortKey('items', path_spec='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']
|
org_id = order['tenant_id']
|
||||||
items = {
|
items = {
|
||||||
item['id']: int(item['quantity'])
|
item['id']: int(item['quantity'])
|
||||||
@@ -54,7 +54,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
item={
|
item={
|
||||||
'id': f'vacancies#{org_id}',
|
'id': f'vacancies#{org_id}',
|
||||||
'sk': f'{order_id}#{uuid4()}',
|
'sk': f'{order_id}#{uuid4()}',
|
||||||
# Post-migration: uncomment the follow lines
|
# Post-migration (orders): uncomment the follow lines
|
||||||
# 'id': f'SLOT#ORG#{org_id}',
|
# 'id': f'SLOT#ORG#{org_id}',
|
||||||
# 'sk': f'ORDER#{order_id}#ENROLLMENT#{uuid4()}',
|
# 'sk': f'ORDER#{order_id}#ENROLLMENT#{uuid4()}',
|
||||||
'course': asdict(slot),
|
'course': asdict(slot),
|
||||||
@@ -81,8 +81,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
class Course:
|
class Course:
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
# Post-migration: remove the following line
|
|
||||||
time_in_days: int
|
|
||||||
|
|
||||||
|
|
||||||
def _get_courses(ids: set) -> tuple[Course, ...]:
|
def _get_courses(ids: set) -> tuple[Course, ...]:
|
||||||
@@ -95,8 +93,6 @@ def _get_courses(ids: set) -> tuple[Course, ...]:
|
|||||||
Course(
|
Course(
|
||||||
id=idx,
|
id=idx,
|
||||||
name=obj['name'],
|
name=obj['name'],
|
||||||
# Post-migration: remove the following line
|
|
||||||
time_in_days=int(obj['access_period']),
|
|
||||||
)
|
)
|
||||||
for idx, obj in result.items()
|
for idx, obj in result.items()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
emailmsg = Message(
|
emailmsg = Message(
|
||||||
from_=EMAIL_SENDER,
|
from_=EMAIL_SENDER,
|
||||||
to=_get_admin_emails(org_id),
|
to=_get_admin_emails(org_id),
|
||||||
|
reply_to=REPLY_TO,
|
||||||
|
bcc=BCC,
|
||||||
subject=SUBJECT.format(month=month),
|
subject=SUBJECT.format(month=month),
|
||||||
)
|
)
|
||||||
emailmsg.add_alternative(MESSAGE.format(month=month))
|
emailmsg.add_alternative(MESSAGE.format(month=month))
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
path_spec='offset_days',
|
path_spec='offset_days',
|
||||||
rename_key='dedup_window_offset_days',
|
rename_key='dedup_window_offset_days',
|
||||||
)
|
)
|
||||||
+ SortKey('konviva')
|
+ SortKey('ORG', rename_key='org')
|
||||||
+ SortKey('tenant', rename_key='org'),
|
+ SortKey('konviva'),
|
||||||
# Post-migration: uncomment the following lines
|
# Post-migration: uncomment the following lines
|
||||||
# + SortKey('KONVIVA', rename_key='konviva')
|
# + SortKey('KONVIVA', rename_key='konviva')
|
||||||
# + SortKey('ORG', rename_key='org'),
|
|
||||||
flatten_top=False,
|
flatten_top=False,
|
||||||
)
|
)
|
||||||
user = User.model_validate(new_image['user'])
|
user = User.model_validate(new_image['user'])
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
':completed': 'COMPLETED',
|
':completed': 'COMPLETED',
|
||||||
':now': now_,
|
':now': now_,
|
||||||
},
|
},
|
||||||
|
exc_cls=StatusConflictError,
|
||||||
)
|
)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
@@ -68,3 +69,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class StatusConflictError(Exception): ...
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
new_image = event.detail['new_image']
|
new_image = event.detail['new_image']
|
||||||
now_ = now()
|
now_ = now()
|
||||||
terms = user_layer.get_item(
|
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['org_id'], 'METADATA#BILLING_TERMS'),
|
||||||
KeyPair(new_image['tenant_id'], 'metadata#billing_policy'),
|
KeyPair(new_image['tenant_id'], 'metadata#billing_policy'),
|
||||||
)
|
)
|
||||||
@@ -48,7 +48,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
item={
|
item={
|
||||||
'id': new_image['id'],
|
'id': new_image['id'],
|
||||||
'sk': 'METADATA#SUBSCRIPTION_COVERED',
|
'sk': 'METADATA#SUBSCRIPTION_COVERED',
|
||||||
'org_id': new_image['tenant_id'],
|
'org_id': new_image['org_id'],
|
||||||
'billing_day': terms['billing_day'],
|
'billing_day': terms['billing_day'],
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ Resources:
|
|||||||
detail:
|
detail:
|
||||||
new_image:
|
new_image:
|
||||||
sk: ["0"]
|
sk: ["0"]
|
||||||
# Post-migration: rename `tenant_id` to `org_id`
|
org_id:
|
||||||
tenant_id:
|
|
||||||
- exists: true
|
- exists: true
|
||||||
|
|
||||||
EventPatchCourseMetadataFunction:
|
EventPatchCourseMetadataFunction:
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ def test_append_cert(
|
|||||||
'name': 'How to Sing Better',
|
'name': 'How to Sing Better',
|
||||||
},
|
},
|
||||||
'cert': {
|
'cert': {
|
||||||
# 'expires_at': '2026-02-10T20:14:42.880991',
|
|
||||||
'expires_at': expires_at.isoformat(),
|
'expires_at': expires_at.isoformat(),
|
||||||
},
|
},
|
||||||
'user': {
|
'user': {
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ def get_enrollments():
|
|||||||
if tenant.id != '*':
|
if tenant.id != '*':
|
||||||
filter_ = [
|
filter_ = [
|
||||||
{
|
{
|
||||||
'attr': 'tenant_id',
|
'attr': 'org_id',
|
||||||
'op': '=',
|
'op': '=',
|
||||||
|
# Post-migration (enrollemnt): rename `tenant` to `org`
|
||||||
'value': tenant.id,
|
'value': tenant.id,
|
||||||
},
|
},
|
||||||
] + filter_
|
] + filter_
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class DeduplicationWindow(BaseModel):
|
|||||||
)
|
)
|
||||||
def deduplication_window(id: str, payload: DeduplicationWindow):
|
def deduplication_window(id: str, payload: DeduplicationWindow):
|
||||||
with enrollment_layer.transact_writer() as transact:
|
with enrollment_layer.transact_writer() as transact:
|
||||||
transact.delete(key=KeyPair(id, 'lock'))
|
transact.delete(key=KeyPair(id, 'LOCK'))
|
||||||
transact.delete(key=KeyPair('lock', payload.lock_hash))
|
transact.delete(key=KeyPair('LOCK', payload.lock_hash))
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ def enroll(
|
|||||||
now_ = now()
|
now_ = now()
|
||||||
user = enrollment.user
|
user = enrollment.user
|
||||||
course = enrollment.course
|
course = enrollment.course
|
||||||
tenant_id = tenant['id']
|
org_id = tenant['id']
|
||||||
lock_hash = md5_hash('%s%s' % (user.id, course.id))
|
lock_hash = md5_hash('%s%s' % (user.id, course.id))
|
||||||
|
|
||||||
with persistence_layer.transact_writer() as transact:
|
with persistence_layer.transact_writer() as transact:
|
||||||
@@ -80,15 +80,15 @@ def enroll(
|
|||||||
item={
|
item={
|
||||||
'sk': '0',
|
'sk': '0',
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
'tenant_id': tenant_id,
|
'org_id': org_id,
|
||||||
**enrollment.model_dump(),
|
**enrollment.model_dump(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': enrollment.id,
|
'id': enrollment.id,
|
||||||
'sk': 'tenant',
|
'sk': 'ORG',
|
||||||
'org_id': tenant_id,
|
'org_id': org_id,
|
||||||
'name': tenant['name'],
|
'name': tenant['name'],
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
|
|||||||
46
http-api/uv.lock
generated
46
http-api/uv.lock
generated
@@ -31,14 +31,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authlib"
|
name = "authlib"
|
||||||
version = "1.6.1"
|
version = "1.6.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cryptography" },
|
{ 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 = [
|
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]]
|
[[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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "jsonlines"
|
name = "jsonlines"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
@@ -509,7 +521,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.9.14"
|
version = "0.10.1"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
@@ -518,6 +530,7 @@ dependencies = [
|
|||||||
{ name = "dictdiffer" },
|
{ name = "dictdiffer" },
|
||||||
{ name = "ftfy" },
|
{ name = "ftfy" },
|
||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
|
{ name = "joserfc" },
|
||||||
{ name = "meilisearch" },
|
{ name = "meilisearch" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "passlib" },
|
{ name = "passlib" },
|
||||||
@@ -525,7 +538,7 @@ dependencies = [
|
|||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "pyjwt" },
|
{ name = "python-multipart" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "smart-open", extra = ["s3"] },
|
{ name = "smart-open", extra = ["s3"] },
|
||||||
@@ -536,11 +549,12 @@ dependencies = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "arnparse", specifier = ">=0.0.2" },
|
{ 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 = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
|
||||||
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
||||||
{ name = "ftfy", specifier = ">=6.3.1" },
|
{ name = "ftfy", specifier = ">=6.3.1" },
|
||||||
{ name = "glom", specifier = ">=24.11.0" },
|
{ name = "glom", specifier = ">=24.11.0" },
|
||||||
|
{ name = "joserfc", specifier = ">=1.2.2" },
|
||||||
{ name = "meilisearch", specifier = ">=0.34.0" },
|
{ name = "meilisearch", specifier = ">=0.34.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "passlib", specifier = ">=1.7.4" },
|
{ name = "passlib", specifier = ">=1.7.4" },
|
||||||
@@ -548,7 +562,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 = "pyjwt", specifier = ">=2.10.1" },
|
{ 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" },
|
||||||
@@ -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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.5"
|
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" },
|
{ 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"
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ def _get_idp_user(
|
|||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
|
||||||
|
# That should be removed when completing the migration
|
||||||
|
# to our own OAuth2 implementation.
|
||||||
client_id = '3ijacqc7r2jc9l4oli2b41f7te'
|
client_id = '3ijacqc7r2jc9l4oli2b41f7te'
|
||||||
client_secret = 'amktf9l40g1mlqdo9fjlcfvpn2cp3mvh4pt97hu55sfelccos58'
|
client_secret = 'amktf9l40g1mlqdo9fjlcfvpn2cp3mvh4pt97hu55sfelccos58'
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ else:
|
|||||||
SQLITE_TABLE = 'courses'
|
SQLITE_TABLE = 'courses'
|
||||||
|
|
||||||
PAPERFORGE_API = 'https://paperforge.saladeaula.digital'
|
PAPERFORGE_API = 'https://paperforge.saladeaula.digital'
|
||||||
TEMPLATE_URI = 's3://saladeaula.digital/billing/template.html'
|
BILLING_TEMPLATE_URI = 's3://saladeaula.digital/billing/template.html'
|
||||||
Reference in New Issue
Block a user