diff --git a/.gitignore b/.gitignore index 3d7c239..584543f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ requirements.txt .dynamodb_volume/ .elastic_volume/ .env +node_modules/ diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index defc7f5..212ae16 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -14,7 +14,7 @@ from aws_lambda_powertools.utilities.typing import LambdaContext from api_gateway import JSONResponse from json_encoder import JSONEncoder -from routes import courses, enrollments, orders, users +from routes import courses, enrollments, orders, orgs, users logger = Logger(__name__) tracer = Tracer() @@ -42,6 +42,7 @@ 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.include_router(orgs.custom_pricing, prefix='/orgs') @app.exception_handler(ServiceError) diff --git a/api.saladeaula.digital/app/routes/courses/__init__.py b/api.saladeaula.digital/app/routes/courses/__init__.py index 59727b6..f30a8c4 100644 --- a/api.saladeaula.digital/app/routes/courses/__init__.py +++ b/api.saladeaula.digital/app/routes/courses/__init__.py @@ -51,17 +51,18 @@ class Course(BaseModel): @router.put('/') -def edit_course(course_id: str): +def put_course(course_id: str): event = router.current_event if not event.decoded_body: raise BadRequestError('Invalid request body') - body = BytesIO(event.decoded_body.encode()) - course = Course.model_validate( - {'id': course_id, 'cert': {}} | parse(event.headers, body), - ) now_ = now() + body = parse( + event.headers, + BytesIO(event.decoded_body.encode()), + ) + course = Course.model_validate({'id': course_id, 'cert': {}} | body) if course.rawfile: object_key = f'certs/templates/{course_id}.html' diff --git a/api.saladeaula.digital/app/routes/enrollments/__init__.py b/api.saladeaula.digital/app/routes/enrollments/__init__.py index 382348a..e09b929 100644 --- a/api.saladeaula.digital/app/routes/enrollments/__init__.py +++ b/api.saladeaula.digital/app/routes/enrollments/__init__.py @@ -1,7 +1,6 @@ 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 layercake.dynamodb import DynamoDBPersistenceLayer, SortKey, TransactKey from boto3clients import dynamodb_client from config import ENROLLMENT_TABLE @@ -20,7 +19,6 @@ dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) @router.get('/') def get_enrollment(enrollment_id: str): - return dyn.collection.get_item( - KeyPair(enrollment_id, '0'), - exc_cls=NotFoundError, + return dyn.collection.get_items( + TransactKey(enrollment_id) + SortKey('0') + SortKey('ORG') + SortKey('LOCK'), ) diff --git a/api.saladeaula.digital/app/routes/orgs/__init__.py b/api.saladeaula.digital/app/routes/orgs/__init__.py new file mode 100644 index 0000000..0d0cf91 --- /dev/null +++ b/api.saladeaula.digital/app/routes/orgs/__init__.py @@ -0,0 +1,4 @@ + +from .custom_pricing import router as custom_pricing + +__all__ = ['custom_pricing'] diff --git a/api.saladeaula.digital/app/routes/orgs/custom_pricing.py b/api.saladeaula.digital/app/routes/orgs/custom_pricing.py new file mode 100644 index 0000000..4f6eb85 --- /dev/null +++ b/api.saladeaula.digital/app/routes/orgs/custom_pricing.py @@ -0,0 +1,17 @@ +from aws_lambda_powertools.event_handler.api_gateway import Router +from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey + +from boto3clients import dynamodb_client +from config import COURSE_TABLE, USER_TABLE + +router = Router() +dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) + + +@router.get('//custompricing') +def get_custom_pricing(org_id: str): + return dyn.collection.query( + PartitionKey(f'CUSTOM_PRICING#ORG#{org_id}'), + table_name=COURSE_TABLE, + limit=100, + ) diff --git a/api.saladeaula.digital/app/routes/register/__init__.py b/api.saladeaula.digital/app/routes/register/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api.saladeaula.digital/template.yaml b/api.saladeaula.digital/template.yaml index a50338a..f82cea0 100644 --- a/api.saladeaula.digital/template.yaml +++ b/api.saladeaula.digital/template.yaml @@ -26,7 +26,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:99 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:100 Environment: Variables: TZ: America/Sao_Paulo diff --git a/api.saladeaula.digital/tests/conftest.py b/api.saladeaula.digital/tests/conftest.py index c320d51..a81fb46 100644 --- a/api.saladeaula.digital/tests/conftest.py +++ b/api.saladeaula.digital/tests/conftest.py @@ -18,6 +18,7 @@ def pytest_configure(): os.environ['TZ'] = 'America/Sao_Paulo' os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME os.environ['USER_TABLE'] = PYTEST_TABLE_NAME + os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME os.environ['BUCKET_NAME'] = 'saladeaula.digital' os.environ['DYNAMODB_PARTITION_KEY'] = PK os.environ['DYNAMODB_SORT_KEY'] = SK diff --git a/api.saladeaula.digital/tests/routes/test_enrollments.py b/api.saladeaula.digital/tests/routes/test_enrollments.py new file mode 100644 index 0000000..d231747 --- /dev/null +++ b/api.saladeaula.digital/tests/routes/test_enrollments.py @@ -0,0 +1,24 @@ +import json +from http import HTTPMethod, HTTPStatus + +from ..conftest import HttpApiProxy, LambdaContext + + +def test_get_enrollment( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/enrollments/578ec87f-94c7-4840-8780-bb4839cc7e64', + method=HTTPMethod.GET, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.OK + + body = json.loads(r['body']) + assert 'user' in body + assert 'course' in body diff --git a/api.saladeaula.digital/tests/seeds.jsonl b/api.saladeaula.digital/tests/seeds.jsonl index 59a280c..d97a802 100644 --- a/api.saladeaula.digital/tests/seeds.jsonl +++ b/api.saladeaula.digital/tests/seeds.jsonl @@ -4,4 +4,7 @@ {"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "emails#sergio@somosbeta.com.br"} // User orgs -{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "ACME", "cnpj": "00000000000191"} \ No newline at end of file +{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "ACME", "cnpj": "00000000000191"} + +// Enrollment +{"id": "578ec87f-94c7-4840-8780-bb4839cc7e64", "sk": "0", "course": {"id": "af3258f0-bccf-4781-aec6-d4c618d234a7", "name": "pytest", "access_period": 180}, "user": {"id": "068b4600-cc36-4b55-b832-bb620021705a", "name": "Benjamin Burnley", "email": "burnley@breakingbenjamin.com"}} \ No newline at end of file diff --git a/api.saladeaula.digital/uv.lock b/api.saladeaula.digital/uv.lock index 3375698..2d063c6 100644 --- a/api.saladeaula.digital/uv.lock +++ b/api.saladeaula.digital/uv.lock @@ -592,7 +592,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.11.0" +version = "0.11.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index f622f9f..6d6dfd8 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -25,7 +25,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:99 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:100 Environment: Variables: TZ: America/Sao_Paulo diff --git a/http-api/uv.lock b/http-api/uv.lock index 633bd61..c80d416 100644 --- a/http-api/uv.lock +++ b/http-api/uv.lock @@ -523,7 +523,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.11.0" +version = "0.11.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, diff --git a/id.saladeaula.digital/app/oauth2.py b/id.saladeaula.digital/app/oauth2.py index 476936f..7060af1 100644 --- a/id.saladeaula.digital/app/oauth2.py +++ b/id.saladeaula.digital/app/oauth2.py @@ -76,6 +76,7 @@ class OpenIDCode(OpenIDCode_): ).filter(scope) if user.scope: + # Used to define permission granularity user_info['scope'] = user.scope return user_info @@ -247,10 +248,13 @@ class RefreshTokenGrant(grants.RefreshTokenGrant): """The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client.""" - logger.debug('Revoking old refresh token', refresh_token=refresh_token) token = getattr(refresh_token, 'refresh_token', None) + logger.debug('Revoking old refresh token', refresh_token=token) user = refresh_token.get_user() + if not token: + return None + with dyn.transact_writer() as transact: transact.delete( key=KeyPair( diff --git a/id.saladeaula.digital/app/routes/authorize.py b/id.saladeaula.digital/app/routes/authorize.py index d944e3d..8cf77fd 100644 --- a/id.saladeaula.digital/app/routes/authorize.py +++ b/id.saladeaula.digital/app/routes/authorize.py @@ -48,11 +48,13 @@ def authorize(): if not client_scopes.issubset(user_scopes): raise ForbiddenError('Access denied') - return server.create_authorization_response( + response = server.create_authorization_response( request=router.current_event, grant_user=sub, grant=grant, ) + + logger.debug(response) except JoseError as err: logger.exception(err) raise BadRequestError(str(err)) @@ -62,6 +64,8 @@ def authorize(): status_code=err.status_code, msg=dict(err.get_body()), # type: ignore ) + else: + return response def _user_scopes(sub: str) -> set: diff --git a/id.saladeaula.digital/app/routes/revoke.py b/id.saladeaula.digital/app/routes/revoke.py index 49d6ba8..f3eb303 100644 --- a/id.saladeaula.digital/app/routes/revoke.py +++ b/id.saladeaula.digital/app/routes/revoke.py @@ -1,13 +1,17 @@ +from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler.api_gateway import Router from oauth2 import RevocationEndpoint, server +logger = Logger(__name__) router = Router() @router.post('/revoke') def revoke(): - return server.create_endpoint_response( + response = server.create_endpoint_response( RevocationEndpoint.ENDPOINT_NAME, router.current_event, ) + logger.debug(response) + return response diff --git a/id.saladeaula.digital/app/routes/token.py b/id.saladeaula.digital/app/routes/token.py index f639e11..191e050 100644 --- a/id.saladeaula.digital/app/routes/token.py +++ b/id.saladeaula.digital/app/routes/token.py @@ -1,10 +1,14 @@ +from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler.api_gateway import Router from oauth2 import server +logger = Logger(__name__) router = Router() @router.post('/token') def issue_token(): - return server.create_token_response(router.current_event) + response = server.create_token_response(router.current_event) + logger.debug(response) + return response diff --git a/id.saladeaula.digital/client/app/routes/authorize.ts b/id.saladeaula.digital/client/app/routes/authorize.ts index d628607..a157286 100644 --- a/id.saladeaula.digital/client/app/routes/authorize.ts +++ b/id.saladeaula.digital/client/app/routes/authorize.ts @@ -59,7 +59,8 @@ export async function loader({ request, context }: Route.LoaderArgs) { Location: loginUrl.toString() } }) - } catch { + } catch (error) { + console.error(error) return new Response(null, { status: httpStatus.INTERNAL_SERVER }) } } diff --git a/id.saladeaula.digital/client/app/routes/index.tsx b/id.saladeaula.digital/client/app/routes/index.tsx index e4253e7..aa0cae2 100644 --- a/id.saladeaula.digital/client/app/routes/index.tsx +++ b/id.saladeaula.digital/client/app/routes/index.tsx @@ -77,7 +77,8 @@ export async function action({ request, context }: Route.ActionArgs) { status: httpStatus.FOUND, headers }) - } catch { + } catch (error) { + console.error(error) return Response.json({}, { status: httpStatus.INTERNAL_SERVER }) } } diff --git a/id.saladeaula.digital/client/app/routes/upstream.ts b/id.saladeaula.digital/client/app/routes/upstream.ts index 4aa1bd2..1588652 100644 --- a/id.saladeaula.digital/client/app/routes/upstream.ts +++ b/id.saladeaula.digital/client/app/routes/upstream.ts @@ -1,21 +1,12 @@ import type { Route } from './+types' -export async function action({ request, context }: Route.ActionArgs) { - const url = new URL(request.url) - const issuerUrl = new URL(url.pathname, context.cloudflare.env.ISSUER_URL) - const r = await fetch(issuerUrl.toString(), { - method: request.method, - headers: request.headers, - body: await request.text() - }) +export const loader = proxy +export const action = proxy - return new Response(await r.text(), { - status: r.status, - headers: r.headers - }) -} - -export async function loader({ request, context }: Route.ActionArgs) { +async function proxy({ + request, + context +}: Route.ActionArgs): Promise { const url = new URL(request.url) const issuerUrl = new URL(url.pathname, context.cloudflare.env.ISSUER_URL) const r = await fetch(issuerUrl.toString(), { @@ -23,6 +14,8 @@ export async function loader({ request, context }: Route.ActionArgs) { headers: request.headers }) + console.log('[response]', r) + return new Response(await r.text(), { status: r.status, headers: r.headers diff --git a/id.saladeaula.digital/template.yaml b/id.saladeaula.digital/template.yaml index de9fff2..d0ff5d0 100644 --- a/id.saladeaula.digital/template.yaml +++ b/id.saladeaula.digital/template.yaml @@ -14,7 +14,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:99 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:100 Environment: Variables: TZ: America/Sao_Paulo diff --git a/konviva-events/template.yaml b/konviva-events/template.yaml index 2f00050..2ff1588 100644 --- a/konviva-events/template.yaml +++ b/konviva-events/template.yaml @@ -20,7 +20,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:99 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:100 Environment: Variables: TZ: America/Sao_Paulo diff --git a/layercake/layercake/dynamodb.py b/layercake/layercake/dynamodb.py index e185a57..121fdab 100644 --- a/layercake/layercake/dynamodb.py +++ b/layercake/layercake/dynamodb.py @@ -1124,6 +1124,7 @@ class DynamoDBCollection: filter_expr: str | None = None, index_forward: bool = False, limit: int = LIMIT, + table_name: str | None = None, ) -> PaginatedResult: """Query returns all items with that partition key or key pair. @@ -1185,6 +1186,7 @@ class DynamoDBCollection: index_forward=index_forward, limit=limit, start_key=_startkey_b64decode(start_key) if start_key else {}, + table_name=table_name, ) items = r['items'] diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 726b380..899d82a 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.11.0" +version = "0.11.1" description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." readme = "README.md" authors = [