This commit is contained in:
2025-07-09 13:44:39 -03:00
parent 690be35634
commit 72b1338135
7 changed files with 66 additions and 27 deletions

View File

@@ -1,4 +1,5 @@
import re import re
import urllib.parse as urllib
OPERATORS = [ OPERATORS = [
'!=', '!=',
@@ -41,7 +42,7 @@ def parse_condition(condition: str) -> dict[str, str] | None:
def parse(s: str) -> list[dict]: def parse(s: str) -> list[dict]:
filter_expr = re.sub(r'\s+', ' ', s).strip() filter_expr = re.sub(r'\s+', ' ', urllib.unquote(s)).strip()
if filter_expr == '': if filter_expr == '':
return [] return []
@@ -59,4 +60,4 @@ def parse(s: str) -> list[dict]:
def encode(conditions: list[dict]): def encode(conditions: list[dict]):
return ' AND '.join(f'{c["attr"]} {c["op"]} {c["value"]}' for c in conditions if c) return ' AND '.join(' '.join(c.values()).strip() for c in conditions if c)

View File

@@ -34,12 +34,12 @@ def get_orders():
sort = event.get_query_string_value('sort', 'create_date:desc') sort = event.get_query_string_value('sort', 'create_date:desc')
page = int(event.get_query_string_value('page', '1')) page = int(event.get_query_string_value('page', '1'))
hits_per_page = int(event.get_query_string_value('hitsPerPage', '25')) hits_per_page = int(event.get_query_string_value('hitsPerPage', '25'))
filter_ = meili.parse(parse.unquote(event.get_query_string_value('filter', ''))) filter_ = meili.parse(event.get_query_string_value('filter', ''))
if tenant.id != '*': if tenant.id != '*':
filter_ = [ filter_ = [
{ {
'attr': 'tenant', 'attr': 'tenant_id',
'op': '=', 'op': '=',
'value': tenant.id, 'value': tenant.id,
}, },

View File

@@ -1,4 +1,4 @@
import json import urllib.parse as parse
from http import HTTPStatus from http import HTTPStatus
from typing import Annotated from typing import Annotated
@@ -6,7 +6,6 @@ from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ( from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError as PowertoolsBadRequestError, BadRequestError as PowertoolsBadRequestError,
) )
from elasticsearch import Elasticsearch
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBCollection, DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
@@ -15,14 +14,15 @@ from layercake.dynamodb import (
TransactKey, TransactKey,
) )
from layercake.extra_types import CpfStr, NameStr from layercake.extra_types import CpfStr, NameStr
from meilisearch import Client as Meilisearch
from pydantic import UUID4, BaseModel, StringConstraints from pydantic import UUID4, BaseModel, StringConstraints
import cognito import cognito
import elastic import meili
from api_gateway import JSONResponse from api_gateway import JSONResponse
from boto3clients import dynamodb_client, idp_client from boto3clients import dynamodb_client, idp_client
from config import ELASTIC_CONN, USER_POOOL_ID, USER_TABLE from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST, USER_POOOL_ID, USER_TABLE
from middlewares import AuditLogMiddleware from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware
from models import User from models import User
from rules.user import update_user from rules.user import update_user
@@ -40,20 +40,45 @@ class BadRequestError(MissingError, PowertoolsBadRequestError):
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError) user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
elastic_client = Elasticsearch(**ELASTIC_CONN) meili_client = Meilisearch(MEILISEARCH_HOST, MEILISEARCH_API_KEY) # noqa: F821
@router.get('/', compress=True, tags=['User'], summary='Get users') @router.get(
'/',
compress=True,
tags=['User'],
middlewares=[
TenantMiddleware(user_layer.collection),
],
summary='Get users',
)
def get_users(): def get_users():
tenant: Tenant = router.context['tenant']
event = router.current_event event = router.current_event
query = event.get_query_string_value('query', '{}') query = parse.unquote(event.get_query_string_value('q', ''))
page_size = event.get_query_string_value('page_size', '25') sort = event.get_query_string_value('sort', 'create_date:desc')
page = int(event.get_query_string_value('page', '1'))
hits_per_page = int(event.get_query_string_value('hitsPerPage', '25'))
filter_ = meili.parse(event.get_query_string_value('filter', ''))
return elastic.search( if tenant.id != '*':
index=USER_TABLE, filter_ = [
page_size=int(page_size), {
query=json.loads(query), 'attr': 'tenant_id',
elastic_client=elastic_client, 'op': '=',
'value': tenant.id,
},
] + filter_
return meili_client.index(USER_TABLE).search(
query,
{
'sort': [sort],
'locales': ['pt'],
'page': page,
'hitsPerPage': hits_per_page,
'filter': meili.encode(filter_),
},
) )

View File

@@ -16,8 +16,8 @@ def create_course(
transact.put( transact.put(
item={ item={
'sk': '0', 'sk': '0',
'tenant': org.id, 'tenant_id': {org.id},
'create_date': now_, 'created_at': now_,
**course.model_dump(), **course.model_dump(),
} }
) )
@@ -27,7 +27,7 @@ def create_course(
'sk': 'metadata#tenant', 'sk': 'metadata#tenant',
'org_id': org.id, 'org_id': org.id,
'name': org.name, 'name': org.name,
'create_date': now_, 'created_at': now_,
} }
) )
@@ -46,7 +46,7 @@ def update_course(
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr='SET #name = :name, access_period = :access_period, \ update_expr='SET #name = :name, access_period = :access_period, \
cert = :cert, update_date = :update_date', cert = :cert, updated_at = :updated_at',
expr_attr_names={ expr_attr_names={
'#name': 'name', '#name': 'name',
}, },
@@ -54,7 +54,7 @@ def update_course(
':name': course.name, ':name': course.name,
':cert': course.cert.model_dump() if course.cert else None, ':cert': course.cert.model_dump() if course.cert else None,
':access_period': course.access_period, ':access_period': course.access_period,
':update_date': now_, ':updated_at': now_,
}, },
cond_expr='attribute_exists(sk)', cond_expr='attribute_exists(sk)',
) )

View File

@@ -9,3 +9,17 @@ def test_parse():
{'attr': 'payment_date', 'op': '>=', 'value': '2025-07-01'}, {'attr': 'payment_date', 'op': '>=', 'value': '2025-07-01'},
{'attr': 'status', 'op': '=', 'value': 'PAID'}, {'attr': 'status', 'op': '=', 'value': 'PAID'},
] ]
assert meili.parse('cnpj%20EXISTS') == [
{'attr': 'cnpj', 'op': 'EXISTS', 'value': ''}
]
def test_encode():
r = meili.encode(
[
{'attr': 'tenant', 'op': '=', 'value': '*'},
{'attr': 'cnpj', 'op': 'EXISTS', 'value': ''},
]
)
assert r == 'tenant = * AND cnpj EXISTS'

View File

@@ -48,8 +48,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
with order_layer.transact_writer() as transact: with order_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(new_image['id'], '0'), key=KeyPair(new_image['id'], '0'),
update_expr='SET tenant = :tenant_id, \ update_expr='SET tenant_id = :tenant_id, updated_at = :updated_at',
updated_at = :updated_at',
expr_attr_values={ expr_attr_values={
':tenant_id': ids['org_id'], ':tenant_id': ids['org_id'],
':updated_at': now_, ':updated_at': now_,

View File

@@ -20,9 +20,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
user_layer.update_item( user_layer.update_item(
key=KeyPair(new_image['sk'], '0'), key=KeyPair(new_image['sk'], '0'),
update_expr='ADD tenant :tenant SET updated_at = :updated_at', update_expr='ADD tenant_id :tenant_id SET updated_at = :updated_at',
expr_attr_values={ expr_attr_values={
':tenant': {tenant}, ':tenant_id': {tenant},
':updated_at': now_, ':updated_at': now_,
}, },
) )