Repository navigation
feat(orders): name the buyer on an order, for callers holding orders.read - #24
Merged
Merged
Conversation
…read
An order said `userId` and nothing else about who bought it, so a back office
could only show a UUID. `customers.read` sits in the permission catalogue with
no route behind it, and building that directory now is a bigger decision than
this screen needs — the buyer of *this order* is order data, the way a name on
an invoice is.
`OrderResponse.buyer` is `{ id, name, email }` for a caller holding
`orders.read`, and null for everyone else, the buyer included. Null rather
than absent so the field is always in the contract and only its value varies.
The three columns are selected and copied one at a time, never spread and
never `include: { user: true }`: `User` carries `passwordHash` and `googleId`,
and the generic form is exactly how a back-office listing hands out every
password hash. A test asserts the select is those three and no more, and
another feeds the mapper a widened row to prove nothing else can get through.
The four back-office transitions now take the caller so their responses obey
the same rule — the panel re-renders the detail from them, and the customer
card would otherwise empty out on every transition. The webhook keeps its
signature and gets null: it has no caller, and it is not a person.
10 new unit tests, 502 total. No new route; the document stays at 42
operations.
The unit tests pin the permission rule against a mocked row. The other half of the claim is about the query itself — that the Prisma include really narrows to three columns — and a mock cannot falsify that. Six tests at the HTTP level: the three fields and no fourth key, both listing shapes, the buyer surviving mark-paid → ship → deliver so the panel's customer card does not empty out, and checkout answering null. One of them serialises the whole response and compares it against the buyer's real bcrypt hash — the value, not the field name. A leak arriving through some other relation, or under some other spelling, still fails it. 43 tests in the orders file, from 37.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Um pedido dizia
userIde mais nada sobre quem comprou, então um back-officesó conseguia mostrar um UUID.
customers.readestá no catálogo de permissõessem rota nenhuma atrás, e construir esse diretório agora é uma decisão maior do
que a tela precisa — o comprador daquele pedido é dado do pedido, do mesmo
jeito que o nome numa nota fiscal.
Nasceu de uma lacuna real: o painel administrativo da AVESSO tem coluna
Clientena lista e um card com nome e e-mail no detalhe, e não havia comopreenchê-los. Caminho upstream em vez de contorno no front-end, conforme
docs/upstream-first.md.O que muda no contrato
42 operações, as mesmas de antes. Nenhuma rota nova: é um campo numa
resposta que já existia, e é por isso que o painel não ganha chamada nenhuma.
buyervem preenchido só para quem carregaorders.read, enullparatodo mundo mais — o próprio comprador incluído.
nullem vez de ausentepara que o campo esteja sempre no contrato e um cliente tipado não tenha que
tratar
undefinedalém denull.nameé anulável porqueUser.nameéString?: uma conta criada peloGoogle nunca passou por
RegisterDto. Quem exibe decide o fallback.Como o vazamento fica fechado
Três colunas escolhidas pelo nome (
{ id: true, name: true, email: true }) ecopiadas uma a uma para a resposta. Nunca spread, nunca
include: { user: true }— a forma genérica é exatamente como uma listagem de back-office distribui
todo hash de senha do banco, e é o item 2 de
docs/admin-api.md.Dois testes seguram isso:
selectsão aqueles três e nenhum a mais(
toEqual, nãotoMatchObject— coluna acrescentada é a regressão);comprador — o valor, não o nome do campo. Um vazamento que chegasse por
outra relação, ou sob outra grafia, ainda falha.
Conferido no documento gerado:
passwordHash,googleId,emailVerifiedAteroleIdnão aparecem.Uma decisão que merece revisão
As quatro transições de back-office (
mark-paid,ship,deliver,refund)passaram a receber o chamador, para que as respostas delas obedeçam à mesma
regra — o painel re-renderiza o detalhe a partir delas, e o card do cliente
esvaziaria a cada transição. O webhook mantém a assinatura e recebe
null:ele não tem chamador, e não é uma pessoa.
Consequência real: um operador com
orders.update_statuse semorders.readenvia o pedido e recebebuyer: null. É o comportamento certo —uma regra só, sem exceção por rota — e está registrado nos edge cases da spec.
A permissão é
orders.reade nãocustomers.readde propósito: quem lê opedido de qualquer pessoa já lê o endereço de entrega dela, que identifica
mais do que um nome.
customers.readcontinua reservada para o diretório declientes, que lista gente sem pedido nenhum e é outra superfície.
Verificação
lint:checktypecheckbuildopenapi:generatepayments-livenão rodou: depende de rede até a Stripe, não toca em nada queesta mudança altera, e a própria docstring dele manda rodar sob demanda.
Spec:
docs/specs/order-buyer.md.