add default org

This commit is contained in:
2026-01-20 14:39:57 -03:00
parent d88c84146c
commit b3e60aea65
11 changed files with 65 additions and 17 deletions

View File

@@ -16,6 +16,7 @@ from exceptions import (
CPFConflictError, CPFConflictError,
EmailConflictError, EmailConflictError,
OrgNotFoundError, OrgNotFoundError,
SubscriptionFrozenError,
UserConflictError, UserConflictError,
UserNotFoundError, UserNotFoundError,
) )
@@ -107,6 +108,14 @@ def _create_user(
try: try:
with dyn.transact_writer() as transact: 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( transact.put(
item=user.model_dump() item=user.model_dump()
| { | {
@@ -223,6 +232,14 @@ def _add_member(user_id: str, org: Org) -> None:
now_ = now() now_ = now()
with dyn.transact_writer() as transact: 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( transact.update(
key=KeyPair(user_id, '0'), key=KeyPair(user_id, '0'),
# Post-migration (users): uncomment the following line # Post-migration (users): uncomment the following line

View File

@@ -17,9 +17,20 @@ def get_orgs(
start_key: Annotated[str | None, Query] = None, start_key: Annotated[str | None, Query] = None,
limit: Annotated[int, Query(ge=25)] = 25, limit: Annotated[int, Query(ge=25)] = 25,
): ):
return dyn.collection.query( r = dyn.collection.query(
# Post-migration (users): rename `orgs#` to `ORG#` # Post-migration (users): uncomment the following line
# key=KeyPair(user_id, 'ORG#'),
key=KeyPair(user_id, 'orgs#'), key=KeyPair(user_id, 'orgs#'),
start_key=start_key, start_key=start_key,
limit=limit, 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 {})

View File

@@ -18,3 +18,5 @@ def test_get_orgs(
) )
assert r['statusCode'] == HTTPStatus.OK assert r['statusCode'] == HTTPStatus.OK
print(r['body'])

View File

@@ -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"} {"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#f6000f79-6e5c-49a0-952f-3bda330ef278", "name": "Banco do Brasil", "cnpj": "00000000000191"}
// Users // 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"} {"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"}

View File

@@ -548,7 +548,7 @@ function PaymentAttemptsMenu({
<EllipsisIcon /> <EllipsisIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" className="w-76"> <PopoverContent align="end" className="w-82">
<div className="p-2 space-y-1.5"> <div className="p-2 space-y-1.5">
{payment_attempts.map(({ sk, brand, last4, status }, index) => { {payment_attempts.map(({ sk, brand, last4, status }, index) => {
const [, , created_at] = sk.split('#') const [, , created_at] = sk.split('#')

View File

@@ -8,6 +8,11 @@ import { request as req } from '@repo/util/request'
export const middleware: Route.MiddlewareFunction[] = [authMiddleware] export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
type Response = {
items: { sk: string }[]
preferred_org_id?: string
}
export async function loader({ context, request }: Route.ActionArgs) { export async function loader({ context, request }: Route.ActionArgs) {
const user = context.get(userContext)! 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 }) throw new Response(await r.text(), { status: r.status })
} }
const { items = [] } = (await r.json()) as { items: { sk: string }[] } const { items = [], preferred_org_id } = (await r.json()) as Response
const [{ sk } = {}] = items const { sk } = preferred_org_id
? (items.find((item) => item.sk.includes(preferred_org_id)) ?? items[0])
: items[0]
if (sk) { if (sk) {
const [_, id] = sk.split('#') const [_, id] = sk.split('#')

View File

@@ -35,6 +35,7 @@ class StatusAttr(Enum):
# EXTERNALLY_PAID = 'paid_at' # EXTERNALLY_PAID = 'paid_at'
EXTERNALLY_PAID = 'payment_date' EXTERNALLY_PAID = 'payment_date'
PAID = 'payment_date' PAID = 'payment_date'
CANCELED = 'canceled_at' CANCELED = 'canceled_at'
REFUNDED = 'refunded_at' REFUNDED = 'refunded_at'
EXPIRED = 'expired_at' EXPIRED = 'expired_at'
@@ -47,10 +48,10 @@ def _status_attr(status: str) -> StatusAttr | None:
return None return None
def _friendly_status(s: str) -> str: def _friendly_status(status: str) -> str:
if 'status' == 'EXTERNALLY_PAID': if 'status' == 'EXTERNALLY_PAID':
return 'PAID' return 'PAID'
return s return status
@app.post('/<order_id>/postback') @app.post('/<order_id>/postback')

View File

@@ -87,7 +87,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
pk=keys['id'], pk=keys['id'],
sk=f'START#{start_period}#END#{end_period}', 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', updated_at = :updated_at',
expr_attr_names={'#status': 'status'}, expr_attr_names={'#status': 'status'},
expr_attr_values={ expr_attr_values={

View File

@@ -101,6 +101,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
key=KeyPair(order_id, 'TRANSACTION#STATS'), key=KeyPair(order_id, 'TRANSACTION#STATS'),
update_expr='SET #count = if_not_exists(#count, :zero) + :one, \ update_expr='SET #count = if_not_exists(#count, :zero) + :one, \
last_attempt_succeeded = :succeeded, \ last_attempt_succeeded = :succeeded, \
created_at = if_not_exists(created_at, :now), \
updated_at = :now', updated_at = :now',
expr_attr_names={ expr_attr_names={
'#count': 'payment_attempts', '#count': 'payment_attempts',
@@ -109,7 +110,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
':succeeded': charge['success'], ':succeeded': charge['success'],
':zero': 0, ':zero': 0,
':one': 1, ':one': 1,
':now': now(), ':now': now_,
}, },
) )

View File

@@ -87,9 +87,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
) )
if 'credit_card' in r: if 'credit_card' in r:
transact.delete(
key=KeyPair(order_id, 'CREDIT_CARD#PAYMENT_INTENT'),
)
transact.put( transact.put(
item={ item={
'id': order_id, 'id': order_id,
@@ -102,6 +99,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
) )
transact.delete(
key=KeyPair(order_id, 'CREDIT_CARD#PAYMENT_INTENT'),
)
if test_mode: if test_mode:
transact.put( transact.put(

View File

@@ -44,7 +44,6 @@ class PaymentMethod(str, Enum):
CREDIT_CARD = 'CREDIT_CARD' CREDIT_CARD = 'CREDIT_CARD'
@dataclass
class Address(BaseModel): class Address(BaseModel):
postcode: str postcode: str
neighborhood: str neighborhood: str
@@ -76,7 +75,7 @@ class Order(BaseModel):
cnpj: str | None = None cnpj: str | None = None
@dataclass @dataclass(frozen=True)
class Credentials: class Credentials:
account_id: str account_id: str
api_token: str api_token: str
@@ -161,6 +160,7 @@ class Iugu:
) )
r.raise_for_status() r.raise_for_status()
except requests.HTTPError as err: except requests.HTTPError as err:
logger.info('IUGU Response', response=err.response)
logger.exception(err) logger.exception(err)
raise raise
else: else:
@@ -197,6 +197,7 @@ class Iugu:
) )
r.raise_for_status() r.raise_for_status()
except requests.HTTPError as err: except requests.HTTPError as err:
logger.info('IUGU Response', response=err.response)
logger.exception(err) logger.exception(err)
raise raise
else: else:
@@ -233,11 +234,13 @@ class Iugu:
try: try:
r = requests.post( 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() r.raise_for_status()
except requests.HTTPError as err: except requests.HTTPError as err:
logger.info('Response', err.response) logger.info('IUGU Response', response=err.response)
logger.exception(err) logger.exception(err)
raise raise
else: else:
@@ -247,9 +250,13 @@ class Iugu:
url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}') url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}')
try: 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() r.raise_for_status()
except requests.HTTPError as err: except requests.HTTPError as err:
logger.info('IUGU Response', response=err.response)
logger.exception(err) logger.exception(err)
raise raise
else: else: