From b3e60aea6560f71e420a9ed5a2a4e6787ae12410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Tue, 20 Jan 2026 14:39:57 -0300 Subject: [PATCH] add default org --- .../app/routes/orgs/users/add.py | 17 +++++++++++++++++ api.saladeaula.digital/app/routes/users/orgs.py | 15 +++++++++++++-- .../tests/routes/users/test_orgs.py | 2 ++ api.saladeaula.digital/tests/seeds.jsonl | 1 + .../_.$orgid.payments.$id._index/route.tsx | 2 +- .../app/routes/_index/route.tsx | 11 +++++++++-- orders-events/app/app.py | 5 +++-- .../app/events/billing/close_window.py | 3 ++- .../app/events/payments/charge_credit_card.py | 3 ++- .../app/events/payments/create_invoice.py | 6 +++--- orders-events/app/iugu.py | 17 ++++++++++++----- 11 files changed, 65 insertions(+), 17 deletions(-) diff --git a/api.saladeaula.digital/app/routes/orgs/users/add.py b/api.saladeaula.digital/app/routes/orgs/users/add.py index 4abe527..719c2cb 100644 --- a/api.saladeaula.digital/app/routes/orgs/users/add.py +++ b/api.saladeaula.digital/app/routes/orgs/users/add.py @@ -16,6 +16,7 @@ from exceptions import ( CPFConflictError, EmailConflictError, OrgNotFoundError, + SubscriptionFrozenError, UserConflictError, UserNotFoundError, ) @@ -107,6 +108,14 @@ def _create_user( try: with dyn.transact_writer() as transact: + transact.condition( + key=KeyPair( + pk='SUBSCRIPTION#FROZEN', + sk=f'ORG#{org.id}', + ), + cond_expr='attribute_not_exists(sk)', + exc_cls=SubscriptionFrozenError, + ) transact.put( item=user.model_dump() | { @@ -223,6 +232,14 @@ def _add_member(user_id: str, org: Org) -> None: now_ = now() with dyn.transact_writer() as transact: + transact.condition( + key=KeyPair( + pk='SUBSCRIPTION#FROZEN', + sk=f'ORG#{org.id}', + ), + cond_expr='attribute_not_exists(sk)', + exc_cls=SubscriptionFrozenError, + ) transact.update( key=KeyPair(user_id, '0'), # Post-migration (users): uncomment the following line diff --git a/api.saladeaula.digital/app/routes/users/orgs.py b/api.saladeaula.digital/app/routes/users/orgs.py index 3c203bb..bc59a54 100644 --- a/api.saladeaula.digital/app/routes/users/orgs.py +++ b/api.saladeaula.digital/app/routes/users/orgs.py @@ -17,9 +17,20 @@ def get_orgs( start_key: Annotated[str | None, Query] = None, limit: Annotated[int, Query(ge=25)] = 25, ): - return dyn.collection.query( - # Post-migration (users): rename `orgs#` to `ORG#` + r = dyn.collection.query( + # Post-migration (users): uncomment the following line + # key=KeyPair(user_id, 'ORG#'), key=KeyPair(user_id, 'orgs#'), start_key=start_key, limit=limit, ) + + preferred, items = None, [] + + for x in r['items']: + if 'DEFAULT' in x['sk']: + preferred = x.get('org_id') + else: + items.append(x) + + return r | {'items': items} | ({'preferred_org_id': preferred} if preferred else {}) diff --git a/api.saladeaula.digital/tests/routes/users/test_orgs.py b/api.saladeaula.digital/tests/routes/users/test_orgs.py index e38ee90..52b563f 100644 --- a/api.saladeaula.digital/tests/routes/users/test_orgs.py +++ b/api.saladeaula.digital/tests/routes/users/test_orgs.py @@ -18,3 +18,5 @@ def test_get_orgs( ) assert r['statusCode'] == HTTPStatus.OK + + print(r['body']) diff --git a/api.saladeaula.digital/tests/seeds.jsonl b/api.saladeaula.digital/tests/seeds.jsonl index 29d075d..d6922c9 100644 --- a/api.saladeaula.digital/tests/seeds.jsonl +++ b/api.saladeaula.digital/tests/seeds.jsonl @@ -1,3 +1,4 @@ +{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#DEFAULT", "org_id": "f6000f79-6e5c-49a0-952f-3bda330ef278"} {"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#f6000f79-6e5c-49a0-952f-3bda330ef278", "name": "Banco do Brasil", "cnpj": "00000000000191"} // Users {"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br", "emails": ["osergiosiqueira@gmail.com", "sergio@somosbeta.combr"], "cpf": "07879819908"} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx index e938bfd..eed76e1 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx @@ -548,7 +548,7 @@ function PaymentAttemptsMenu({ - +
{payment_attempts.map(({ sk, brand, last4, status }, index) => { const [, , created_at] = sk.split('#') diff --git a/apps/admin.saladeaula.digital/app/routes/_index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_index/route.tsx index 8235754..d95f210 100644 --- a/apps/admin.saladeaula.digital/app/routes/_index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_index/route.tsx @@ -8,6 +8,11 @@ import { request as req } from '@repo/util/request' export const middleware: Route.MiddlewareFunction[] = [authMiddleware] +type Response = { + items: { sk: string }[] + preferred_org_id?: string +} + export async function loader({ context, request }: Route.ActionArgs) { const user = context.get(userContext)! @@ -21,8 +26,10 @@ export async function loader({ context, request }: Route.ActionArgs) { throw new Response(await r.text(), { status: r.status }) } - const { items = [] } = (await r.json()) as { items: { sk: string }[] } - const [{ sk } = {}] = items + const { items = [], preferred_org_id } = (await r.json()) as Response + const { sk } = preferred_org_id + ? (items.find((item) => item.sk.includes(preferred_org_id)) ?? items[0]) + : items[0] if (sk) { const [_, id] = sk.split('#') diff --git a/orders-events/app/app.py b/orders-events/app/app.py index c7a25eb..f8a361e 100644 --- a/orders-events/app/app.py +++ b/orders-events/app/app.py @@ -35,6 +35,7 @@ class StatusAttr(Enum): # EXTERNALLY_PAID = 'paid_at' EXTERNALLY_PAID = 'payment_date' PAID = 'payment_date' + CANCELED = 'canceled_at' REFUNDED = 'refunded_at' EXPIRED = 'expired_at' @@ -47,10 +48,10 @@ def _status_attr(status: str) -> StatusAttr | None: return None -def _friendly_status(s: str) -> str: +def _friendly_status(status: str) -> str: if 'status' == 'EXTERNALLY_PAID': return 'PAID' - return s + return status @app.post('//postback') diff --git a/orders-events/app/events/billing/close_window.py b/orders-events/app/events/billing/close_window.py index c298c2d..96091ad 100644 --- a/orders-events/app/events/billing/close_window.py +++ b/orders-events/app/events/billing/close_window.py @@ -87,7 +87,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: pk=keys['id'], sk=f'START#{start_period}#END#{end_period}', ), - update_expr='SET #status = :status, s3_uri = :s3_uri, \ + update_expr='SET #status = :status, \ + s3_uri = :s3_uri, \ updated_at = :updated_at', expr_attr_names={'#status': 'status'}, expr_attr_values={ diff --git a/orders-events/app/events/payments/charge_credit_card.py b/orders-events/app/events/payments/charge_credit_card.py index b5dd6ea..4b690da 100644 --- a/orders-events/app/events/payments/charge_credit_card.py +++ b/orders-events/app/events/payments/charge_credit_card.py @@ -101,6 +101,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: key=KeyPair(order_id, 'TRANSACTION#STATS'), update_expr='SET #count = if_not_exists(#count, :zero) + :one, \ last_attempt_succeeded = :succeeded, \ + created_at = if_not_exists(created_at, :now), \ updated_at = :now', expr_attr_names={ '#count': 'payment_attempts', @@ -109,7 +110,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ':succeeded': charge['success'], ':zero': 0, ':one': 1, - ':now': now(), + ':now': now_, }, ) diff --git a/orders-events/app/events/payments/create_invoice.py b/orders-events/app/events/payments/create_invoice.py index 30a1f5a..e16318f 100644 --- a/orders-events/app/events/payments/create_invoice.py +++ b/orders-events/app/events/payments/create_invoice.py @@ -87,9 +87,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ) if 'credit_card' in r: - transact.delete( - key=KeyPair(order_id, 'CREDIT_CARD#PAYMENT_INTENT'), - ) transact.put( item={ 'id': order_id, @@ -102,6 +99,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: }, cond_expr='attribute_not_exists(sk)', ) + transact.delete( + key=KeyPair(order_id, 'CREDIT_CARD#PAYMENT_INTENT'), + ) if test_mode: transact.put( diff --git a/orders-events/app/iugu.py b/orders-events/app/iugu.py index 2586304..96bbb06 100644 --- a/orders-events/app/iugu.py +++ b/orders-events/app/iugu.py @@ -44,7 +44,6 @@ class PaymentMethod(str, Enum): CREDIT_CARD = 'CREDIT_CARD' -@dataclass class Address(BaseModel): postcode: str neighborhood: str @@ -76,7 +75,7 @@ class Order(BaseModel): cnpj: str | None = None -@dataclass +@dataclass(frozen=True) class Credentials: account_id: str api_token: str @@ -161,6 +160,7 @@ class Iugu: ) r.raise_for_status() except requests.HTTPError as err: + logger.info('IUGU Response', response=err.response) logger.exception(err) raise else: @@ -197,6 +197,7 @@ class Iugu: ) r.raise_for_status() except requests.HTTPError as err: + logger.info('IUGU Response', response=err.response) logger.exception(err) raise else: @@ -233,11 +234,13 @@ class Iugu: try: r = requests.post( - url, json=payload, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT) + url, + json=payload, + timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT), ) r.raise_for_status() except requests.HTTPError as err: - logger.info('Response', err.response) + logger.info('IUGU Response', response=err.response) logger.exception(err) raise else: @@ -247,9 +250,13 @@ class Iugu: url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}') try: - r = requests.get(url, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT)) + r = requests.get( + url, + timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT), + ) r.raise_for_status() except requests.HTTPError as err: + logger.info('IUGU Response', response=err.response) logger.exception(err) raise else: