9.7 KiB
Arquitetura
Contexto
O portal é uma aplicação independente e complementar ao Snipe-IT. Usa um monólito modular para reduzir custo operacional, mas separa os domínios internamente para evitar acoplamento entre inventário, reservas, aprovações e operações.
Browser
│ HTTPS
▼
Nginx ──► Laravel + Inertia/React
├── PostgreSQL (estado do portal)
├── Redis (cache, filas, locks)
├── SMTP/Mailpit
└── Snipe-IT REST API
Responsabilidade dos sistemas
| Informação/operação | Sistema oficial |
|---|---|
| Etiqueta, série, modelo, categoria e localização habitual | Snipe-IT |
| Estado operacional e atribuição física atual | Snipe-IT |
| Checkout e check-in efetivos | Snipe-IT via API |
| Pedidos futuros e calendário | Portal |
| Aprovação, rejeição, cancelamento e conflitos | Portal |
| Entrega/devolução e evidência operacional | Portal, sincronizada com Snipe-IT |
| Auditoria do fluxo de requisição | Portal |
Módulos
app/Domain/Identity: perfis, permissões e identidades externas.app/Domain/Administration: políticas persistentes, modelos de notificação e configuração funcional.app/Domain/Inventory: cache local de ativos e consultas do catálogo.app/Domain/Reservations: disponibilidade, estados, regras e requisições.app/Domain/Approvals: decisões, histórico e concorrência de aprovação.app/Domain/Operations: entrega, devolução, faltas, atrasos e incidentes.app/Domain/Audit: logs imutáveis pela interface.app/Integrations/SnipeIt: cliente HTTP, DTOs, mapper e exceções da API Snipe-IT.app/Integrations/Microsoft: cliente OIDC de tenant único, descoberta e validação de tokens.
Identidade institucional
O login Microsoft usa Authorization Code Flow com OIDC e PKCE. O portal descobre authorization_endpoint, token_endpoint, emissor e jwks_uri no documento .well-known específico do tenant. O id_token só é aceite após validação RS256, expiração, emissor, audiência, tenant e nonce; state é consumido uma única vez e o fluxo expira ao fim de dez minutos. A rotação de chaves provoca uma atualização imediata do JWKS em cache e uma segunda validação.
A identidade externa é associada explicitamente a uma conta local já autenticada, após confirmação recente da palavra-passe. Uma coincidência de email nunca cria nem associa contas automaticamente. O portal persiste apenas sub, tenant, email apresentado e um subconjunto de claims de perfil; códigos e tokens não são guardados. Utilizadores inativos continuam impedidos de entrar. O login local permanece disponível como contingência operacional.
Consistência temporal
Os períodos usam [starts_at, ends_at): uma reserva que termina às 11:00 não entra em conflito com outra que começa às 11:00.
A defesa ocorre em duas camadas:
AvailabilityServiceproduz erros de negócio legíveis;- PostgreSQL mantém a garantia final com uma restrição
EXCLUDE USING gistnos estados bloqueantes.
Uma criação ou aprovação deve bloquear as linhas dos ativos, voltar a validar dentro da transação e tolerar corrida através da restrição da base de dados.
AvailabilityService::availableAssets() aplica as mesmas condições em SQL para alimentar catálogo e formulário sem carregar todo o inventário em memória. O período consultado é propagado até à criação da requisição; a validação transacional continua a ser a garantia aplicacional final.
Pré-reservas ficam bloqueantes em held enquanto aguardam decisão. ReservationService centraliza criação, cancelamento e expiração: todas as transições bloqueiam a requisição, atualizam os itens bloqueantes e criam um AuditLog. Um job único procura prazos vencidos a cada cinco minutos. A restrição GiST continua a proteger contra corridas entre estes ciclos de vida.
ApprovalService bloqueia a requisição e os ativos antes de aprovar ou substituir, revalida a disponibilidade e regista cada passo em approval_actions e audit_logs. Uma substituição cancela o item original e cria um novo held, apenas na mesma categoria. Pedidos e respostas de esclarecimento conservam o histórico e renovam explicitamente o prazo.
Notificações implementam ShouldQueue, usam afterCommit e seguem dois canais: email e tabela notifications. A caixa interna consulta exclusivamente a relação polimórfica do utilizador autenticado, evitando acesso horizontal a notificações de terceiros.
ReservationPolicy fornece defaults versionados e sobreposições guardadas em portal_settings. Antecedência, duração, limite de itens e prazo são validados no pedido e no serviço de domínio. A margem operacional é materializada em reservation_items.buffer_starts_at/buffer_ends_at; a restrição GiST usa estes campos, mantendo a proteção contra corridas. Uma alteração de política só afeta novas reservas, preservando o contrato temporal das existentes.
Os relatórios usam consultas de leitura com período máximo de 366 dias. A exportação CSV é transmitida em blocos e neutraliza conteúdos interpretáveis como fórmulas. Auditoria, relatórios e calendário global têm Gates próprios; o perfil auditor permanece exclusivamente de leitura.
NotificationTemplateService mantém defaults no código e sobreposições em portal_settings. Apenas variáveis conhecidas ({reference}, {requester}, {starts_at}, {ends_at}, {comment}) são substituídas; a alteração de modelos é auditada e nunca inclui credenciais ou configuração SMTP.
Fluxo de dados Snipe-IT
- Um job consulta a API paginada com token de leitura.
- Um mapper reduz a resposta aos campos documentados.
AssetSyncServicefaz upsert porsnipe_asset_id, calculacontent_hashe marca cada ativo comlast_seen_sync_run_id.- Apenas após todas as páginas serem recebidas com sucesso, ativos cujo marcador não corresponde à execução atual ficam
active = false; não são apagados para preservar histórico. - Antes de aprovação ou entrega, o ativo é validado novamente.
- Imediatamente antes de cada checkout, o portal faz um
GET /hardware/{id}com o token de leitura e bloqueia a operação se existir qualquer atribuição remota. - Checkout/check-in usam um token operacional separado e guardam apenas a resposta necessária para auditoria.
- Antes de um check-in, o portal exige um checkout confirmado em
handover_recordspara o mesmo requisitante e confirma porGETque o ativo continua atribuído a esse utilizador Snipe-IT.
HandoverService mantém a ordem explícita “Snipe-IT primeiro, confirmação local depois” para cada equipamento. Uma requisição com vários itens não é apresentada como concluída se apenas parte dos movimentos for aceite: os itens confirmados preservam o seu HandoverRecord, os restantes mantêm o estado anterior e a requisição passa a incident. As escritas externas não têm retry automático porque um timeout pode ocorrer depois de o Snipe-IT executar a operação; repetir sem reconciliação poderia criar um resultado enganador.
O checkout associa o ativo a um utilizador Snipe-IT. O operador confirma o ID remoto na primeira entrega e o portal guarda-o em users.snipe_user_id para utilizações futuras. Um ativo já atribuído nunca recebe um checkout do portal. O check-in repõe o estado e a localização habitual sincronizados do ativo, mas apenas quando o movimento de saída foi confirmado pelo portal e a atribuição remota ainda corresponde ao mesmo destinatário. As respostas externas ficam em handover_records.snipe_response; tokens nunca são persistidos.
A instância Snipe-IT ligada ao projeto é de produção e é tratada como inventário imutável. O portal não cria, edita nem elimina ativos, modelos, produtos ou outra informação mestre. Os únicos pedidos de escrita autorizados no cliente são os endpoints estritos de checkout e check-in, invocados por uma ação operacional legítima. Diagnósticos, sincronizações e validações técnicas usam exclusivamente GET.
Uma falha intermédia pode deixar upserts válidos das páginas anteriores, mas nunca executa a desativação global. A sincronização seguinte converge o cache. Um lock distribuído e um job único impedem duas sincronizações simultâneas.
Referências oficiais usadas para o contrato: autenticação da API, listagem de hardware, limites/paginação, checkout de hardware, check-in de hardware e formato de erros da API.
Segurança
- Sem registo público.
- Cookies seguros e HTTPS em produção.
- Policies em cada ação de domínio.
- Tokens e segredos apenas no ambiente.
- Dois tokens Snipe-IT com menor privilégio.
- MFA obrigatório a definir para perfis privilegiados antes do piloto.
- Logs de auditoria não expostos a operações de update/delete.
Saúde e recuperação
/health/live confirma que o processo HTTP responde. /health/ready confirma acesso à base de dados e um ciclo de escrita/leitura no cache, devolvendo apenas o estado global e nunca detalhes de infraestrutura. O health check do Nginx usa readiness.
Os scripts em ops/ produzem um pg_dump custom com permissões restritas, validam a estrutura e permitem restaurar para uma base temporária descartável. Encriptação, retenção, cópia externa e agendamento pertencem à plataforma de produção e são acompanhados pelo checklist operacional.