Profile Server API

Visão geral do sistema

Como as peças se encaixam — atores, portões de entrada, cálculo de escopo e ciclo de vida do documento.

Documento de visão geral para discussão: mostra o caminho que um dado percorre do login até o banco, onde ficam as decisões de acesso e quais pontos ainda estão em aberto. Para o detalhe de cada rota, veja Endpoints; para as camadas de código, Arquitetura.

1. O que o sistema é

O Profile Server é o cadastro central de cooperativas (organizations) e seus documentos, compartilhado por vários aplicativos e vários clientes. Ele resolve três perguntas:

  1. Quem é você — autenticação delegada ao Keycloak.
  2. O que você alcança — um usuário só enxerga as cooperativas alcançadas pelos tenants de que participa.
  3. Os documentos estão em dia — upload, revisão (aprovado/rejeitado) e validade.

2. O fluxo completo em uma imagem

Do login até a resposta, com os dois caminhos de entrada lado a lado. O traço verde é o caminho feliz do usuário final, o roxo é o app-to-app, e os vermelhos finos são as saídas de erro. Para slide ou impressão, use os arquivos exportados: PNG · SVG · fonte .mmd.

Bloco 2 simplificado — pré-datava a persona por tenant

O ramo "X-Tenant-Id informado?" abaixo ainda mostra a lógica anterior a 2026-07-30 (header ausente → sempre união). Hoje isso só vale pra persona global; quem não é global e não manda o header é resolvido sozinho se tiver 1 tenant só, ou toma 403 com 0 ou 2+. A resolução de persona também acontece no mesmo passo, não depois. Lógica exata e atualizada em Autenticação. Os arquivos exportados (PNG/SVG/.mmd) ainda não foram regerados.

3. Panorama — quem fala com quem

O painel nunca fala com o banco: tudo passa pela API. O Keycloak é a única fonte de identidade — desde 2026-07-30 (PLANO-PERSONA-POR-TENANT.md) nenhuma persona vem mais de lá. O banco guarda quem é o usuário e o que ele pode fazer: UserTenant.persona (por tenant) e User.type === 'Global' (bypass total, não é por tenant). O Keycloak só carrega as roles de exceção (modulo:acao[:deny]) além da identidade.

4. Os dois portões de entrada

Toda rota nasce sob um de dois prefixos, com middlewares e modelos de autorização diferentes. Essa separação é a decisão estrutural mais importante do sistema.

/api/*/service/*
Consumidorpessoa no paineloutro sistema
TokenJWT de usuárioclient credentials
Quem é o atorreq.user (IUser)req.service (tenant resolvido pelo azp)
AutorizaçãoRolesGuard (persona × módulo × ação)apenas "o client existe e está ativo"
Escopo de dadoscooperativas dos tenants do usuáriotenant único do próprio client

Ponto de atenção: o allowedScopes do tenant existe no schema mas é passado como lista vazia — não há autorização granular em /service/*. Qualquer client cadastrado e ativo chega em qualquer rota de serviço. Ver §10.

5. Ciclo de vida de uma requisição /api

Dois pontos que costumam surpreender:

  • A autorização (persona) e o escopo (quais cooperativas) são independentes — e vêm de fontes diferentes. O RolesGuard decide se você pode chamar a rota olhando a persona (banco, UserTenant.persona ou User.type se global) + roles de exceção do Keycloak; o escopo decide quais linhas voltam e é injetado no Prisma a partir do User.type (Admin/Global não são limitados; Cooperative/Technician/Coordinator são). Um 403 nunca vem do tenant, e uma lista vazia nunca vem da persona.
  • O filtro é injetado na camada de banco, não nos use cases. Nenhum repositório de domínio sabe que existe multi-tenancy — o que torna impossível esquecer o filtro em uma query nova, mas também torna o comportamento invisível na leitura do código do use case.

6. Como o escopo é calculado

Este é o coração do multi-tenant: transformar "quem é o usuário" em uma lista de cooperativas.

Simplificado — pré-datava a persona por tenant

Igual ao bloco 2: o ramo abaixo (header ausente → sempre união) é o comportamento anterior a 2026-07-30. Hoje "ausente" só vira união pra persona global; pra quem não é global, vira inferência automática (1 tenant só) ou 403 (0 ou 2+ tenants). O mesmo passo também resolve a persona (UserTenant.persona), não só o escopo de organizations. Lógica exata em Autenticação.

A lista resultante escopa, de uma vez só, Organization (pelo próprio id) e Document, OrganizationAddress e OrganizationContact (por organizationId). É por isso que trocar o tenant ativo encolhe a tela inteira, e não uma listagem só.

Hierarquia de dados

Tenant é o cruzamento de um App com um Customer — é a unidade que amarra tudo: usuários entram por UserTenant, cooperativas por OrganizationTenant. Os dois vínculos são M:N, então a mesma cooperativa pode ser alcançada por tenants diferentes (e é isso que torna o seletor de tenant ativo necessário).

7. Ciclo de vida de um documento

O DocumentType define as regras (obrigatório, exige aprovação, tem validade, dias de validade padrão, exige código de autenticidade); o Document é a instância daquela regra para uma cooperativa. Vencimento é calculado, não persistido: não existe status EXPIRED nem agendador que reavalie documentos — hoje, um documento vencido continua APPROVED no banco até alguém olhar.

8. Camadas do código

Regra de ouro: domain nunca importa de infra. Use cases devolvem Either em vez de lançar exceções, e o controller traduz Left em status HTTP.

9. O painel

Uma rota por área do menu, cada uma protegida por RequireRole no cliente — proteção de usabilidade, não de segurança: a decisão real é sempre do backend.

RotaMóduloQuem vê
/customers, /apps, /tenantsplataformaglobal
/usersusuáriosglobal
/organizations, /documents, /document-typesoperaçãotodas as personas (com ações variando)

O painel busca em GET /api/me/permissions a lista de modulo:acao liberados e usa isso para esconder menu e botão. O seletor de tenant ativo fica no rodapé da barra lateral e só aparece para quem tem dois ou mais tenants.

10. Pontos em aberto — pauta sugerida

Levantados do código e dos planos já escritos no repositório.

Segurança e autorização

PontoSituaçãoImpacto
PermissionGuard (UMA)importado mas nunca registrado — o APP_GUARD está comentado@Permissions(...) está em todo controller e não executa. Ou se remove, ou se decide ativar
allowedScopes em /service/*passado como [], nunca validadoqualquer client ativo alcança qualquer rota de serviço
Restrição de cooperativas por usuárioespecificado em PLANO-RESTRICAO-ORGANIZACAO-POR-USUARIO.md, não implementadohoje todo usuário de um tenant vê todas as cooperativas dele
users fora do tenant-adminexclusão temporária desde 2026-07-17, revisitada e mantida em 2026-07-30tenant-admin não administra os próprios usuários, nem atribui persona a eles no tenant

Operação e confiabilidade

PontoSituaçãoImpacto
Cache de autenticaçãogravação comentada no auth.middlewaretoda requisição faz verificação de JWT, userinfo no Keycloak e consulta ao banco
Cache de tenant em /service/*lê a chave tenant_scope1: e grava tenant_scope:o cache nunca acerta; toda chamada de serviço vai ao banco
console.log de userInfo e rolesativos no middlewaredados pessoais do usuário indo para o log a cada requisição
Testes ponta a ponta266 testes unitários, zero e2eo encaixe controller + guard + Prisma não é exercitado automaticamente
Vencimento de documentosem status próprio e sem rotinaninguém é avisado quando um documento obrigatório vence

Produto

PontoSituação
Padronização de errosPLANO-PADRONIZACAO-ERROS.md aprovado, não implementado (código estável + envelope + tradução no painel)
Criação de usuário via serviçoPLANO-CRIACAO-USUARIO-SERVICE.md
Tenant ativoimplementado (seletor "Representando" no painel + header X-Tenant-Id)
Persona por tenantimplementado (2026-07-30) — PLANO-PERSONA-POR-TENANT.md. Persona deixou de vir do Keycloak: UserTenant.persona por tenant, User.type === 'Global' pro bypass total