improve support to meili

This commit is contained in:
2025-07-08 19:59:21 -03:00
parent 4a7e3de9a9
commit 690be35634
9 changed files with 165 additions and 18 deletions

62
http-api/app/meili.py Normal file
View File

@@ -0,0 +1,62 @@
import re
OPERATORS = [
'!=',
'>=',
'<=',
'=',
'>',
'<',
'TO',
'EXISTS',
'IN',
'NOT IN',
]
def remove_quotes(value: str) -> str:
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
return value[1:-1]
return value
def parse_condition(condition: str) -> dict[str, str] | None:
for op in OPERATORS:
parts = condition.split(op)
if len(parts) == 2:
attr, value = parts
attr = attr.strip()
value = remove_quotes(value.strip())
return {
'attr': attr,
'op': op,
'value': value,
}
return None
def parse(s: str) -> list[dict]:
filter_expr = re.sub(r'\s+', ' ', s).strip()
if filter_expr == '':
return []
parts = re.split(r'\b(?:AND|OR)\b', filter_expr)
conditions = []
for part in parts:
condition = parse_condition(part.strip())
if condition:
conditions.append(condition)
return conditions
def encode(conditions: list[dict]):
return ' AND '.join(f'{c["attr"]} {c["op"]} {c["value"]}' for c in conditions if c)

View File

@@ -1,15 +1,14 @@
import urllib.parse as parse
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,
SortKey,
TransactKey,
)
from meilisearch import Client as Meilisearch
import meili
from boto3clients import dynamodb_client
from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST, ORDER_TABLE, USER_TABLE
from middlewares import Tenant, TenantMiddleware
@@ -35,10 +34,16 @@ def get_orders():
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_ = parse.unquote(event.get_query_string_value('filter', ''))
filter_ = meili.parse(parse.unquote(event.get_query_string_value('filter', '')))
if tenant.id != '*':
filter_ = f'tenant = {tenant.id}'
filter_ = [
{
'attr': 'tenant',
'op': '=',
'value': tenant.id,
},
] + filter_
return meili_client.index(ORDER_TABLE).search(
query,
@@ -47,7 +52,7 @@ def get_orders():
'locales': ['pt'],
'page': page,
'hitsPerPage': hits_per_page,
'filter': filter_,
'filter': meili.encode(filter_),
},
)
@@ -59,7 +64,11 @@ def get_orders():
summary='Get order',
)
def get_order(id: str):
return order_layer.collection.get_item(
KeyPair(id, '0'),
exc_cls=NotFoundError,
return order_layer.collection.get_items(
TransactKey(id)
+ SortKey('0')
+ SortKey('items', path_spec='items')
+ SortKey('address')
+ SortKey('nfse', path_spec='nfse')
+ SortKey('fees', path_spec='fees'),
)

View File

@@ -4,7 +4,7 @@
{"sk": {"S": "items"}, "id": {"S": "XhAgiMsDG9rLhkN9urFru3"}, "items": {"L": [{"M": {"name": {"S": "NR-10 B\u00e1sico"}, "id": {"S": "38"}, "quantity": {"N": "1"}, "unit_price": {"N": "149"}}}]}}
{"qrcode": {"S": "https://faturas.iugu.com/qr_code/d61cb3b6-da4d-48af-8497-4e2ed1f2f92f-27f9"}, "sk": {"S": "pix"}, "id": {"S": "XhAgiMsDG9rLhkN9urFru3"}, "qrcode_text": {"S": "00020101021226840014br.gov.bcb.pix2562qr.iugu.com/public/payload/v2/D61CB3B6DA4D48AF84974E2ED1F2F92F5204000053039865406149.005802BR5922MACIEL DOS SANTOS CIA6013FLORIANOPOLIS62070503***6304068E"}, "create_date": {"S": "2023-12-28T10:19:49.867940-03:00"}}
{"sk": {"S": "user"}, "user_id": {"S": "c57MXV4BByFZLajcvBHdnN"}, "id": {"S": "XhAgiMsDG9rLhkN9urFru3"}, "create_date": {"S": "2023-12-28T10:20:54.229700-03:00"}}
{"payment_method": {"S": "CREDIT_CARD"}, "status": {"S": "PAID"}, "assignee": {"M": {"name": {"S": "Alessandra Larivia"}}}, "total": {"N": "149"}, "installments": {"N": "1"}, "due_date": {"S": "2024-02-08T08:46:59.771233-03:00"}, "email": {"S": "financeiro@aquanobile.com.br"}, "name": {"S": "AQUA NOBILE SERVI\u00c7OS LTDA"}, "create_date": {"S": "2024-02-08T08:41:59.773135-03:00"}, "payment_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "phone_number": {"S": "+553130705599"}, "sk": {"S": "0"}, "cnpj": {"S": "11278500000106"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "update_date": {"S": "2024-02-08T08:42:10.910170-03:00"}}
{"payment_method": {"S": "CREDIT_CARD"}, "status": {"S": "PAID"}, "assignee": {"M": {"name": {"S": "Alessandra Larivia"}}}, "total": {"N": "149"}, "installments": {"N": "1"}, "due_date": {"S": "2024-02-08T08:46:59.771233-03:00"}, "email": {"S": "financeiro@aquanobile.com.br"}, "name": {"S": "AQUA NOBILE SERVI\u00c7OS LTDA"}, "create_date": {"S": "2024-02-08T08:41:59.773135-03:00"}, "payment_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "phone_number": {"S": "+553130705599"}, "sk": {"S": "0"}, "cnpj": {"S": "11278500000106"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "update_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "tenant": {"S": "123"}}
{"city": {"S": "Nova Lima"}, "postcode": {"S": "34006065"}, "neighborhood": {"S": "Vila da Serra"}, "complement": {"S": "sala 211"}, "sk": {"S": "address"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "street_number": {"S": "1021"}, "street": {"S": "Alameda Oscar Niemeyer"}, "state": {"S": "MG"}}
{"last4": {"S": "6188"}, "brand": {"S": "Mastercard"}, "sk": {"S": "credit_card"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}}
{"sk": {"S": "fees"}, "fees": {"N": "5"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T08:42:13.440374-03:00"}}
@@ -25,7 +25,6 @@
{"city": {"S": "Marab\u00e1"}, "postcode": {"S": "68501535"}, "neighborhood": {"S": "Cidade Nova"}, "complement": {"S": ""}, "sk": {"S": "address"}, "id": {"S": "3Gkayh3FBuCSMW5eE5NfH5"}, "street_number": {"S": "S/N"}, "street": {"S": "Rua Ivete Vargas"}, "state": {"S": "PA"}}
{"sk": {"S": "items"}, "id": {"S": "3Gkayh3FBuCSMW5eE5NfH5"}, "items": {"L": [{"M": {"name": {"S": "NR-10 Complementar (SEP)"}, "id": {"S": "55"}, "quantity": {"N": "6"}, "unit_price": {"N": "149"}}}]}}
{"sk": {"S": "lock"}, "id": {"S": "3Gkayh3FBuCSMW5eE5NfH5"}, "lock_type": {"S": "CNPJ"}, "create_date": {"S": "2024-02-08T07:52:14.397071-03:00"}}
{"sk": {"S": "nfse"}, "nfse": {"S": "10382"}, "id": {"S": "3Gkayh3FBuCSMW5eE5NfH5"}, "create_date": {"S": "2024-02-08T07:52:14.361212-03:00"}}
{"sk": {"S": "user"}, "user_id": {"S": "EkvQwpmmL6vzWtJunM5dCJ"}, "id": {"S": "3Gkayh3FBuCSMW5eE5NfH5"}, "create_date": {"S": "2024-02-08T07:48:56.154952-03:00"}}
{"payment_method": {"S": "CREDIT_CARD"}, "status": {"S": "DECLINED"}, "assignee": {"M": {"name": {"S": "ALEXANDRE ROZIN"}}}, "total": {"N": "605"}, "installments": {"N": "2"}, "due_date": {"S": "2024-02-05T09:30:16.163998-03:00"}, "email": {"S": "alexandre@hartinst.com.br"}, "name": {"S": "HARTINST MANUTEN\u00c7\u00c3O INDUSTRIAL"}, "create_date": {"S": "2024-02-05T09:25:16.166315-03:00"}, "payment_date": {"NULL": true}, "phone_number": {"S": "+551936010400"}, "sk": {"S": "0"}, "cnpj": {"S": "31958303000145"}, "id": {"S": "mFERanksjRywrAniUdVmoV"}, "update_date": {"S": "2024-02-05T09:25:28.662366-03:00"}}
{"city": {"S": "Americana"}, "postcode": {"S": "13467600"}, "neighborhood": {"S": "Parque Novo Mundo"}, "complement": {"S": ""}, "sk": {"S": "address"}, "id": {"S": "mFERanksjRywrAniUdVmoV"}, "street_number": {"S": "3929"}, "street": {"S": "Avenida de Cillo"}, "state": {"S": "SP"}}
@@ -41,3 +40,4 @@
{"sk": {"S": "user"}, "user_id": {"S": "mESNbpk4pMTASxtnstd37B"}, "id": {"S": "bTRW6w69aYYKjKy3FZeVjt"}, "create_date": {"S": "2024-02-06T10:28:06.906367-03:00"}}
{"id": {"S": "QV4sXY3DvSTUMGJ4QqsrwJ"},"sk": {"S": "0"},"assignee": {"M": {"name": {"S": "Sérgio Rafael de Siqueira"}}},"cnpj": {"S": "15608435000190"},"create_date": {"S": "2024-11-02T19:39:57.174669-03:00"},"due_date": {"S": "2024-11-03T19:39:09.897000-03:00"},"email": {"S": "sergio@somosbeta.com.br"},"name": {"S": "Beta Educação"},"payment_date": {"S": "2024-11-02T19:40:12.143000-03:00"},"payment_method": {"S": "MANUAL"},"status": {"S": "PAID"},"total": {"N": "10"},"update_date": {"S": "2024-11-02T19:40:12.831120-03:00"}}
{"id": {"S": "QV4sXY3DvSTUMGJ4QqsrwJ"},"sk": {"S": "generated_items#7bzQhaPEB9uR8jRkzkFe2h"},"create_date": {"S": "2025-04-11T15:08:00.256341-03:00"},"status": {"S": "SUCCESS"}}
{"id": {"S": "123"}, "sk": {"S": "0"}}

View File

@@ -40,7 +40,7 @@ Globals:
COURSE_TABLE: !Ref CourseTable
ELASTIC_CLOUD_ID: "{{resolve:ssm:/betaeducacao/elastic/cloud_id/str}}"
ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}"
KONVIVA_API_URL: https://saladeaula.digital
KONVIVA_API_URL: https://lms.saladeaula.digital
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}"
MEILISEARCH_HOST: https://meili.eduseg.com.br
MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}"

View File

@@ -21,6 +21,7 @@ def pytest_configure():
os.environ['USER_TABLE'] = PYTEST_TABLE_NAME
os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
@dataclass
@@ -40,6 +41,7 @@ class HttpApiProxy:
*,
headers: dict = {},
auth_flow_type: str = 'USER_AUTH',
queryStringParameters: dict = {},
**kwargs,
) -> dict:
return {
@@ -49,10 +51,7 @@ class HttpApiProxy:
'rawQueryString': 'parameter1=value1&parameter1=value2&parameter2=value',
'cookies': ['cookie1', 'cookie2'],
'headers': headers,
'queryStringParameters': {
'parameter1': 'value1,value2',
'parameter2': 'value',
},
'queryStringParameters': queryStringParameters,
'requestContext': {
'accountId': '123456789012',
'apiId': 'api-id',

View File

@@ -0,0 +1,48 @@
import json
import urllib.parse as parse
from http import HTTPMethod, HTTPStatus
from ..conftest import HttpApiProxy, LambdaContext
def test_orders(
mock_app,
dynamodb_seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
# This data was added from seeds
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/orders',
queryStringParameters={
'filter': parse.quote('status = PENDING AND due_date >= 202025-07-09'),
},
method=HTTPMethod.GET,
headers={'x-tenant': 'cJtK9SsnJhKPyxESe7g3DG'},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
def test_get_order(
mock_app,
dynamodb_seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
# This data was added from seeds
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/orders/KpZTYvu4RzgMJW3A2DF6cC',
method=HTTPMethod.GET,
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
body = json.loads(r['body'])
print(body)

View File

@@ -23,3 +23,12 @@
{"id": {"S": "cpf"}, "sk": {"S": "08679004901"}}
{"id": {"S": "lock"}, "sk": {"S": "c2116a43f8f1aed659a10c83dab17ed3"}}
{"id": {"S": "vacancies#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "3CNrFB9dy2RLit2pdeUWy4#8c9b55ef-e988-43ee-b2da-8594850605d7"}}
{"payment_method": {"S": "CREDIT_CARD"}, "status": {"S": "PAID"}, "assignee": {"M": {"name": {"S": "Alessandra Larivia"}}}, "total": {"N": "149"}, "installments": {"N": "1"}, "due_date": {"S": "2024-02-08T08:46:59.771233-03:00"}, "email": {"S": "financeiro@aquanobile.com.br"}, "name": {"S": "AQUA NOBILE SERVI\u00c7OS LTDA"}, "create_date": {"S": "2024-02-08T08:41:59.773135-03:00"}, "payment_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "phone_number": {"S": "+553130705599"}, "sk": {"S": "0"}, "cnpj": {"S": "11278500000106"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "update_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "tenant": {"S": "cJtK9SsnJhKPyxESe7g3DG"}}
{"city": {"S": "Nova Lima"}, "postcode": {"S": "34006065"}, "neighborhood": {"S": "Vila da Serra"}, "complement": {"S": "sala 211"}, "sk": {"S": "address"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "street_number": {"S": "1021"}, "street": {"S": "Alameda Oscar Niemeyer"}, "state": {"S": "MG"}}
{"last4": {"S": "6188"}, "brand": {"S": "Mastercard"}, "sk": {"S": "credit_card"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}}
{"sk": {"S": "fees"}, "fees": {"N": "5"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T08:42:13.440374-03:00"}}
{"sk": {"S": "invoice"}, "pdf": {"S": "https://faturas.iugu.com/cdc228a0-bd1a-44aa-aa0d-1b47bad74aed-873a.pdf"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T08:42:03.041809-03:00"}, "invoice_id": {"S": "cdc228a0-bd1a-44aa-aa0d-1b47bad74aed-873a"}}
{"sk": {"S": "items"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "items": {"L": [{"M": {"name": {"S": "NR-33 Supervisor em Espa\u00e7o Confinado"}, "id": {"S": "57"}, "quantity": {"N": "1"}, "unit_price": {"N": "149"}}}]}}
{"sk": {"S": "lock"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "lock_type": {"S": "CNPJ"}, "create_date": {"S": "2024-02-08T08:42:13.058916-03:00"}}
{"sk": {"S": "nfse"}, "nfse": {"S": "10384"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T09:05:03.879692-03:00"}}
{"sk": {"S": "user"}, "user_id": {"S": "5AZXXXCWa2bU4spsxfLznx"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T08:42:05.190415-03:00"}}

View File

@@ -0,0 +1,9 @@
import json
import urllib.parse as parse
def test_filter():
f = '[{%22attr%22%3A%22status%22%2C%22op%22%3A%22%3D%22%2C%22value%22%3A%22PENDING%22}]'
decoded = parse.unquote(f)
filtros = json.loads(decoded)
print(filtros)

View File

@@ -0,0 +1,11 @@
import meili
def test_parse():
s = 'payment_date >= 2025-07-01 AND status = PAID'
r = meili.parse(s)
assert r == [
{'attr': 'payment_date', 'op': '>=', 'value': '2025-07-01'},
{'attr': 'status', 'op': '=', 'value': 'PAID'},
]