This commit is contained in:
2025-05-16 14:29:14 -03:00
parent 17131380ac
commit cc9bd08daa
49 changed files with 177 additions and 54 deletions

View File

@@ -26,7 +26,6 @@ Example
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from typing import Any from typing import Any
import boto3
from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.utilities.data_classes import event_source from aws_lambda_powertools.utilities.data_classes import event_source
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import ( from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
@@ -34,13 +33,14 @@ from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event i
APIGatewayAuthorizerResponseV2, APIGatewayAuthorizerResponseV2,
) )
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
import boto3
from botocore.endpoint_provider import Enum from botocore.endpoint_provider import Enum
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
from layercake.funcs import pick from layercake.funcs import pick
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from cognito import get_user from cognito import get_user
from settings import USER_TABLE from conf import USER_TABLE
APIKEY_PREFIX = 'sk-' APIKEY_PREFIX = 'sk-'

View File

@@ -2,11 +2,11 @@ from dataclasses import asdict, dataclass
from urllib.parse import quote as urlquote from urllib.parse import quote as urlquote
from urllib.parse import urlencode, urlparse from urllib.parse import urlencode, urlparse
import requests
from aws_lambda_powertools.event_handler.exceptions import BadRequestError from aws_lambda_powertools.event_handler.exceptions import BadRequestError
from glom import glom from glom import glom
import requests
from settings import KONVIVA_API_URL, KONVIVA_SECRET_KEY from conf import KONVIVA_API_URL, KONVIVA_SECRET_KEY
class KonvivaError(BadRequestError): class KonvivaError(BadRequestError):

View File

@@ -1,3 +1,4 @@
from auth import AuthFlowType
from aws_lambda_powertools.event_handler.api_gateway import ( from aws_lambda_powertools.event_handler.api_gateway import (
APIGatewayHttpResolver, APIGatewayHttpResolver,
Response, Response,
@@ -8,9 +9,6 @@ from aws_lambda_powertools.event_handler.middlewares import (
) )
from pydantic import UUID4, BaseModel, EmailStr, Field from pydantic import UUID4, BaseModel, EmailStr, Field
from auth import AuthFlowType
class User(BaseModel): class User(BaseModel):
id: str id: str
name: str name: str

View File

@@ -1,5 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from auth import AuthFlowType
from aws_lambda_powertools.event_handler.api_gateway import ( from aws_lambda_powertools.event_handler.api_gateway import (
APIGatewayHttpResolver, APIGatewayHttpResolver,
Response, Response,
@@ -16,11 +17,8 @@ from aws_lambda_powertools.event_handler.middlewares import (
from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair
from pydantic import UUID4, BaseModel from pydantic import UUID4, BaseModel
from auth import AuthFlowType
from .authentication_middleware import User from .authentication_middleware import User
class Tenant(BaseModel): class Tenant(BaseModel):
id: UUID4 | str id: UUID4 | str
name: str name: str

View File

@@ -7,15 +7,15 @@ from meilisearch import Client as Meilisearch
from api_gateway import JSONResponse from api_gateway import JSONResponse
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware from conf import (
from models import Course, Org
from rules.course import create_course, update_course
from settings import (
COURSE_TABLE, COURSE_TABLE,
MEILISEARCH_API_KEY, MEILISEARCH_API_KEY,
MEILISEARCH_HOST, MEILISEARCH_HOST,
USER_TABLE, USER_TABLE,
) )
from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware
from models import Course, Org
from rules.course import create_course, update_course
router = Router() router = Router()

View File

@@ -11,12 +11,12 @@ from layercake.dynamodb import (
) )
from pydantic import UUID4, BaseModel from pydantic import UUID4, BaseModel
import elastic
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from conf import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE
import elastic
from middlewares.audit_log_middleware import AuditLogMiddleware from middlewares.audit_log_middleware import AuditLogMiddleware
from middlewares.authentication_middleware import User from middlewares.authentication_middleware import User
from rules.enrollment import set_status_as_canceled from rules.enrollment import set_status_as_canceled
from settings import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE
from .vacancies import router as vacancies from .vacancies import router as vacancies

View File

@@ -7,11 +7,11 @@ from layercake.dynamodb import (
) )
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from middlewares import Tenant, TenantMiddleware from conf import (
from settings import (
ENROLLMENT_TABLE, ENROLLMENT_TABLE,
USER_TABLE, USER_TABLE,
) )
from middlewares import Tenant, TenantMiddleware
router = Router() router = Router()

View File

@@ -6,7 +6,7 @@ from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search from elasticsearch_dsl import Search
from layercake.funcs import pick from layercake.funcs import pick
from settings import ELASTIC_CONN, USER_TABLE from conf import ELASTIC_CONN, USER_TABLE
router = Router() router = Router()
elastic_client = Elasticsearch(**ELASTIC_CONN) elastic_client = Elasticsearch(**ELASTIC_CONN)

View File

@@ -11,9 +11,9 @@ from layercake.dynamodb import (
KeyPair, KeyPair,
) )
import elastic
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from settings import ELASTIC_CONN, ORDER_TABLE from conf import ELASTIC_CONN, ORDER_TABLE
import elastic
router = Router() router = Router()
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)

View File

@@ -1,4 +1,5 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Literal
from aws_lambda_powertools.event_handler import Response, content_types from aws_lambda_powertools.event_handler import Response, content_types
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
@@ -12,11 +13,10 @@ from layercake.dynamodb import (
TransactKey, TransactKey,
) )
from pydantic.main import BaseModel from pydantic.main import BaseModel
from typing_extensions import Literal
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from conf import USER_TABLE
from rules.org import update_policies from rules.org import update_policies
from settings import USER_TABLE
router = Router() router = Router()
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)

View File

@@ -6,10 +6,10 @@ from layercake.dynamodb import (
PrefixKey, PrefixKey,
) )
import konviva
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from conf import USER_TABLE
import konviva
from middlewares import User from middlewares import User
from settings import USER_TABLE
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)

View File

@@ -1,5 +1,5 @@
import json
from http import HTTPStatus from http import HTTPStatus
import json
from typing import Annotated from typing import Annotated
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
@@ -17,14 +17,14 @@ from layercake.dynamodb import (
from layercake.extra_types import CpfStr, NameStr from layercake.extra_types import CpfStr, NameStr
from pydantic import UUID4, BaseModel, StringConstraints from pydantic import UUID4, BaseModel, StringConstraints
import cognito
import elastic
from api_gateway import JSONResponse from api_gateway import JSONResponse
from boto3clients import dynamodb_client, idp_client from boto3clients import dynamodb_client, idp_client
import cognito
from conf import ELASTIC_CONN, USER_POOOL_ID, USER_TABLE
import elastic
from middlewares import AuditLogMiddleware from middlewares import AuditLogMiddleware
from models import User from models import User
from rules.user import update_user from rules.user import update_user
from settings import ELASTIC_CONN, USER_POOOL_ID, USER_TABLE
from .emails import router as emails from .emails import router as emails
from .logs import router as logs from .logs import router as logs

View File

@@ -15,9 +15,9 @@ from pydantic import BaseModel, EmailStr
from api_gateway import JSONResponse from api_gateway import JSONResponse
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from conf import USER_TABLE
from middlewares import AuditLogMiddleware from middlewares import AuditLogMiddleware
from rules.user import add_email, del_email, set_email_as_primary from rules.user import add_email, del_email, set_email_as_primary
from settings import USER_TABLE
class BadRequestError(MissingError, PowertoolsBadRequestError): ... class BadRequestError(MissingError, PowertoolsBadRequestError): ...

View File

@@ -11,7 +11,7 @@ from layercake.dynamodb import (
) )
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from settings import USER_TABLE from conf import USER_TABLE
from .orgs import router as orgs from .orgs import router as orgs

View File

@@ -16,9 +16,9 @@ from pydantic import BaseModel
from api_gateway import JSONResponse from api_gateway import JSONResponse
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from conf import USER_TABLE
from middlewares.audit_log_middleware import AuditLogMiddleware from middlewares.audit_log_middleware import AuditLogMiddleware
from rules.user import del_org_member from rules.user import del_org_member
from settings import USER_TABLE
class BadRequestError(MissingError, PowertoolsBadRequestError): ... class BadRequestError(MissingError, PowertoolsBadRequestError): ...

View File

@@ -4,7 +4,7 @@ from uuid import uuid4
from layercake.dateutils import now from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems
from settings import ORDER_TABLE from conf import ORDER_TABLE
class Author(TypedDict): class Author(TypedDict):

View File

@@ -0,0 +1,62 @@
import pprint
from meilisearch import Client as Meilisearch
MASTER_KEY = 'zrYPsSAG1hgq2zB1dkF0sB9xLoIwTLAz6uw38pWRf5abdpTjY2eeMTIsfPbDbqQR'
API_KEY = '1aa4c720611269e9425e8467df7e802f3a20ad6c5f31fe875ac886fc4efa2c83'
client = Meilisearch(
# 'https://meili.vps.eduseg.com.br',
# '1aa4c720611269e9425e8467df7e802f3a20ad6c5f31fe875ac886fc4efa2c83',
'http://localhost:7700'
)
pp = pprint.PrettyPrinter(indent=4)
courses = client.index('test-courses')
courses.update_settings(
{
'sortableAttributes': ['create_date'],
'filterableAttributes': ['tenant__org_id'],
}
)
# with open('cli/search-results.json') as fp:
# docs = json.load(fp)
# courses.add_documents(docs)
# pp.pprint(courses.search(''))
# client.create_index('betaeducacao-prod-orders', {'primaryKey': 'id'})
# client.create_index('betaeducacao-prod-enrollments', {'primaryKey': 'id'})
# client.create_index('betaeducacao-prod-users_d2o3r5gmm4it7j', {'primaryKey': 'id'})
# An index is where the documents are stored.
# index = client.index('users')
# pp.pprint(index.search(query='*'))
# documents = [
# {'id': 1, 'title': 'Carol', 'genres': ['Romance', 'Drama']},
# {'id': 2, 'title': 'Wonder Woman', 'genres': ['Action', 'Adventure']},
# {'id': 3, 'title': 'Life of Pi', 'genres': ['Adventure', 'Drama']},
# {
# 'id': 4,
# 'title': 'Mad Max: Fury Road',
# 'genres': ['Adventure', 'Science Fiction'],
# },
# {'id': 5, 'title': 'Moana', 'genres': ['Fantasy', 'Action']},
# {'id': 6, 'title': 'Philadelphia', 'genres': ['Drama']},
# ]
# # # If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.
# index.add_documents(documents
#
# )
# pp.pprint(client.get_keys({'limit': 3}).model_dump_json())
#
# uid='fdbdda56-00dd-4f53-934f-6629f3b08ee3' name=None description='Add, get documents and search' actions=['documents.add', 'documents.get', 'search'] indexes=['users', 'courses', 'enrollments', 'orders'] expires_at=None key='1aa4c720611269e9425e8467df7e802f3a20ad6c5f31fe875ac886fc4efa2c83' created_at=datetime.datetime(2025, 4, 1, 0, 46, 27, 751365) updated_at=datetime.datetime(2025, 4, 1, 0, 46, 27, 751365)

8
http-api/cli/ddb.py Normal file
View File

@@ -0,0 +1,8 @@
import json
from layercake.dynamodb import serialize
with open('cli/search-results.json') as fp:
docs = json.load(fp)
for doc in docs:
print(json.dumps(serialize(doc)))

View File

@@ -1,8 +1,8 @@
from typing import Any, Generator from typing import Any, Generator
import boto3 import boto3
import jsonlines
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
import jsonlines
from layercake.dynamodb import deserialize from layercake.dynamodb import deserialize
from tqdm import tqdm from tqdm import tqdm

40
http-api/cli/unzip.py Normal file
View File

@@ -0,0 +1,40 @@
import gzip
from pathlib import Path
def unzip_gzip(file: Path, target: str):
with gzip.open(file, 'rb') as r:
with open(target, 'wb') as w:
return w.write(r.read())
def unzip_gzfiles(dirpath: Path) -> None:
"""Unzip the .gz files from a dir."""
if not dirpath.exists() or not dirpath.is_dir():
print(f'"{dirpath}" not found')
return None
gzfiles = list(dirpath.glob('*.gz'))
if not gzfiles:
print(f'No .gz files found in "{dirpath}"')
return None
# Create a directory to output the files
Path(f'{dirpath}/out').mkdir(exist_ok=True)
for file in gzfiles:
filename = file.name.removesuffix('.gz')
if not file.is_file():
continue
if unzip_gzip(file, f'{dirpath}/out/{filename}'):
print(f'Unzipped "{file.name}"')
if __name__ == '__main__':
try:
dirpath = input('Type the directory path\n')
unzip_gzfiles(Path(dirpath))
except (KeyboardInterrupt, Exception):
print('See ya')

9
http-api/env.json Normal file
View File

@@ -0,0 +1,9 @@
{
"Parameters": {
"KONVIVA_API_KEY": "ac982dd94dbfab5067182d9adb88d3da",
"USER_TABLE": "test-users",
"ORDER_TABLE": "test-orders",
"ENROLLMENT_TABLE": "test-enrollments",
"COURSE_TABLE": "test-courses"
}
}

View File

@@ -16,10 +16,23 @@ dev = [
] ]
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = ["app/"]
addopts = "--cov --cov-report html -v" addopts = "--cov --cov-report html -v"
[tool.ruff]
target-version = "py311"
src = ["app"]
[tool.ruff.format] [tool.ruff.format]
quote-style = "single" quote-style = "single"
[tool.ruff.lint]
select = ["E", "F", "I"]
[tool.ruff.lint.isort]
lines-between-types = 1
force-sort-within-sections = true
[tool.uv.sources] [tool.uv.sources]
layercake = { path = "../layercake" } layercake = { path = "../layercake" }

View File

@@ -0,0 +1,3 @@
{
"extraPaths": ["app/"]
}

View File

@@ -17,13 +17,13 @@ Parameters:
Globals: Globals:
Function: Function:
CodeUri: . CodeUri: app/
Runtime: python3.13 Runtime: python3.13
Tracing: Active Tracing: Active
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:47 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:48
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -1,12 +1,12 @@
import base64 import base64
import json
import os
from dataclasses import dataclass from dataclasses import dataclass
from http import HTTPMethod from http import HTTPMethod
import json
import os
import jsonlines import jsonlines
import pytest
from layercake.dynamodb import DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBPersistenceLayer
import pytest
PYTEST_TABLE_NAME = 'pytest' PYTEST_TABLE_NAME = 'pytest'
PK = os.getenv('DYNAMODB_PARTITION_KEY') PK = os.getenv('DYNAMODB_PARTITION_KEY')

View File

@@ -1,5 +1,5 @@
import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
import json
from layercake.dynamodb import ( from layercake.dynamodb import (
ComposeKey, ComposeKey,

View File

@@ -10,7 +10,6 @@ from layercake.dynamodb import (
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
def test_vacancies( def test_vacancies(
mock_app, mock_app,
dynamodb_seeds, dynamodb_seeds,

View File

@@ -1,9 +1,8 @@
import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
import json
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
def test_lookup( def test_lookup(
mock_app, mock_app,
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,

View File

@@ -1,11 +1,10 @@
import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
import json
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
def test_get_policies( def test_get_policies(
mock_app, mock_app,
dynamodb_seeds, dynamodb_seeds,

View File

@@ -1,9 +1,8 @@
import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
import json
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
def test_settings( def test_settings(
mock_app, mock_app,
dynamodb_seeds, dynamodb_seeds,

View File

@@ -1,5 +1,5 @@
import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
import json
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBCollection, DynamoDBCollection,
@@ -11,7 +11,6 @@ from layercake.dynamodb import (
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
def test_update_user_cpf( def test_update_user_cpf(
mock_app, mock_app,
dynamodb_seeds, dynamodb_seeds,

View File

@@ -3,7 +3,6 @@ from auth import _parse_bearer_token
from .conftest import LambdaContext from .conftest import LambdaContext
def test_bearer_jwt(lambda_context: LambdaContext): def test_bearer_jwt(lambda_context: LambdaContext):
# You should mock the Cognito user to pass the test # You should mock the Cognito user to pass the test
app.get_user = lambda *args, **kwargs: { app.get_user = lambda *args, **kwargs: {

View File

@@ -1,5 +1,5 @@
from conf import KONVIVA_API_URL
import konviva import konviva
from settings import KONVIVA_API_URL
def test_konviva_token(): def test_konviva_token():

View File

@@ -1,14 +1,12 @@
from http import HTTPMethod from http import HTTPMethod
import pytest
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
from middlewares import AuthenticationMiddleware, TenantMiddleware from middlewares import AuthenticationMiddleware, TenantMiddleware
import pytest
from .conftest import HttpApiProxy, LambdaContext from .conftest import HttpApiProxy, LambdaContext
@pytest.fixture @pytest.fixture
def mock_app(dynamodb_persistence_layer: DynamoDBPersistenceLayer): def mock_app(dynamodb_persistence_layer: DynamoDBPersistenceLayer):
collect = DynamoDBCollection(dynamodb_persistence_layer) collect = DynamoDBCollection(dynamodb_persistence_layer)