[DÚVIDA] Implementando um Registro de Histórico de Alterações para Auditoria em uma Aplicação Web de Gestão de Carros

Tecnologias Utilizadas:

  • Frontend: NEXT
  • API: NEST
  • ORM: PRISMA
  • Banco de Dados: POSTRESQL

Descrição:

Estou desenvolvendo uma aplicação web para gestão de carros e gostaria de implementar um registro de histórico de alterações para fins de auditoria futura.

Cenário:

Na minha aplicação, cada carro possui uma página correspondente acessível através da rota "localhost/cars/5" (onde o número 5 representa o ID do carro). Essa página é dividida em várias abas, incluindo "HISTÓRICO", onde desejo armazenar informações sobre as alterações feitas no veículo.

Exemplo de Tabela de Histórico:

A tabela a seguir representa um exemplo de como as informações podem ser registradas:

DATA USUÁRIO DETALHES IP
24/05/2023 LUCAS ALTEROU A PLACA DE XYZET5 PARA ZBR3DA 192.168.0.125
22/05/2023 ROBERT CRIOU O CARRO 192.168.0.125

Perguntas:

  1. Como posso implementar o registro de histórico de alterações na minha aplicação?
  2. Quais tecnologias devo utilizar para alcançar esse objetivo?
  3. Existe algum framework ou biblioteca recomendada que possa me auxiliar nesse processo?
  4. Como tratar essas alterações para mostrar nos detalhes e comparar, por exemplo, se o usuário além de editar a placa, editar a filial do carro, "ALTEROU A PLACA DE XYZET5 PARA ZBR3DA E FILIAL DE SÃO PAULO PARA RIO DE JANEIRO".

Exemplo em Outra Linguagem:

Para ajudar na compreensão do registro de histórico de alterações, você pode conferir um exemplo já implementado em outra linguagem. Acesse o seguinte link público:

https://demo.sgp.net.br/admin/cliente/41/historico/

Credenciais de acesso:

Usuário: demo Senha: demo

Agradeço antecipadamente pela ajuda da comunidade!

  1. Para implementar o registro de histórico de alterações, você pode criar uma tabela de histórico que terá um relacionamento direto com a tabela principal (neste caso, a tabela de carros). Sempre que uma alteração for feita, você adicionará um registro no histórico.

  2. As tecnologias que você já está usando (Next, Nest, Prisma, PostgreSQL) são suficientes para alcançar esse objetivo. Você não precisa de nenhuma tecnologia adicional.

  3. Não há necessidade de um framework ou biblioteca específica para isso. Você pode conseguir isso com as tecnologias que já está usando.

  4. Para tratar essas alterações e mostrar nos detalhes, você pode fazer uma lógica manual para verificar quais são as alterações. Por exemplo, antes de fazer a atualização, você pode comparar os dados antigos com os novos e registrar as diferenças na tabela de histórico. Aqui está um exemplo de como você pode fazer isso com o Prisma:

async update(id: number, data: UpdateCarDto, userId: number) {
    // Get the old data
    const oldData = await this.prisma.cars.findUnique({ where: { id } });

    // Compare the old data with the new one and create a description of the changes
    let description = '';
    if (oldData.plate !== data.plate) {
        description += `ALTEROU A PLACA DE ${oldData.plate} PARA ${data.plate} `;
    }
    if (oldData.branch !== data.branch) {
        description += `E FILIAL DE ${oldData.branch} PARA ${data.branch}`;
    }

    // Update the car
    await this.prisma.cars.update({
        where: { id },
        data,
    });

    // Add a record to the history
    await this.prisma.history.create({
        data: {
            carId: id,
            userId,
            details: description,
            ip: '192.168.0.125', // You should get the real IP here
        },
    });
}

Vc até consegue ir pro caminho de criar algo genérico no Prisma com os Middlewares, mas pode dar um pouco de dor de cabeça. No entanto, acho que vale uma POC (Proof of Concept) pra vc experimentar o Middleware do Prisma e ver se resolveria pra vc

Referência: https://www.prisma.io/docs/concepts/components/prisma-client/middleware

Colocando o middleware em um service do Nest, ficaria algo assim:

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Injectable()
export class AuditService {
  constructor(private prisma: PrismaService) {
    this.prisma.$use(async (params, next) => {
      if (params.model !== 'Car' || params.action !== 'update') {
        return next(params);
      }

      const oldData = await this.prisma.car.findUnique({ where: { id: params.args.where.id } });
      const result = await next(params);

      let description = '';
      if (oldData.plate !== result.plate) {
        description += `ALTEROU A PLACA DE ${oldData.plate} PARA ${result.plate} `;
      }
      if (oldData.branch !== result.branch) {
        description += `E FILIAL DE ${oldData.branch} PARA ${result.branch}`;
      }

      await this.prisma.history.create({
        data: {
          carId: params.args.where.id,
          userId: userId, // You should get the real user ID here
          details: description,
          ip: '192.168.0.125', // You should get the real IP here
        },
      });

      return result;
    });
  }
}

OBS: não testei nenhum código, então é mais pra ter uma ideia de caminho

Caraca Paulo, você é uma ciência, muito obrigado pela força, deu-me um norte, pois estava perdido. Amanhã ja vou começar a tentar fazer a implementação, trarei novidades aqui. Recomendo muito o canal dele: [YouTube](https://m.youtube.com/c/paulosalvatore)

Meu comentário não responde suas perguntas, mas passa uma visão de uma implementação que já fiz e que talvez possa ser útil para você.

Nessa implementação, considerei que todas as tabelas do sistema poderiam ser auditadas, ou seja, não seria legal criar relacionamento entre tabela de auditoria e demais. Para tal, minha tabela de auditoria possuía um campo do tipo varchar que receberia a tabela auditada, o registro antigo em formato json, o registro atualizado em formato json e uma leitura humana.

Ficando mais ou menos assim:

Tabela auditoria:

Id Tabela RegistroAntigo RegistroNovo LeituraHumana CriadoEm
1 Cars {"id": 133, "modelo": "gol", "ano": 2010} {"id": 133, "modelo": "gol", "ano": 2023} Campo "ano" alterado de "2010" para "2023" 2022-03-04 22:33:00
Obrigado megbr, essa visão foi muito boa e fez sentido para mim também, irei implementar um conjunto.

no gerador de crud (phpcrud.ceuvago.com) que fiz ao gerar os códigos com login ativado, você verá que no arquivo API quando o sistema faz requisição de update/, realiza a consulta na base pelo id do item e salva seus dados na tabela auditoria com acrescento de datatime e tipo de mudança o mesmo para requisição delete. recomendo dar uma olhada, quando gerar o codigo é mostrado no browser sem necessidade de baixar

Eu estou justamente implementando algumas POC´s para poder validar qual a melhor solução. No meu caso a necessidade é manter além do histórico da modificação registrar dados do usuário autenticado no sistema, dito isso criei uma classe que permita registrar via campos do tipo JSON as informações, como a ideia é vários sistemas utilizarem tal solução implemente essa poc utilizando Fiber (Golang), usando banco de dados PostgreSQL.

Segue link do repositório https://github.com/guilhermecarvalhocarneiro/go-estudo-fiber-auditoria

Esse é meu primeiro projeto em Go, inclusive nem está com arquitetura limpa.

Grato pela resposta Gui, como nao sei nada sobre Golang ficou complicado para mim, mas vou recomendar a sua POC para meu colega de trabalho, ele usa essa linguagem de progamação, irá ajudar muito!

Como o uriel comentou, basicamente você criaria uma tabela de histórico (o nome que preferir) que teria um relacionamento direto com a tabela principal (carros imagino) a partir dai sempre que o status for alterado você adiciona um registro no histórico

Grato pela resposta, victordev13, eu respondi lá as complicações.

vc usa qual banco de dados? Posso dar um exemplo com algum banco relacional!

Cria uma tabela chamada: Alterações Campos:

id(primary key), data, id_carro(foreign key da tabela carro) id_pessoa(foreign key da tabela de pessoas cadastradas), descrição(da alteração), ip

Este é um exemplo simples que da pra fazer facilmente! O banco pode ser PostgreSQL, MariaDB ou outro relacional!

Com o banco mongoDB da pra fazer isso tbm! Embora ele não seja um banco relacional da pra fazr relações com ele. Desde que não sejam muito complexas!

Ou usar o documento carro e dentro dele colocar as alterações. Lembrando um documento cabe 16 mega(o que é bastante)

Grato pela resposta, contudo, como tratar essas alterações para mostrar nos detalhes e comparar, por exemplo, se o usuário além de editar a placa, editar a filial do carro? Ex: toda vez que eu usar esta função de update do Nest com Prisma ```ts update(id: number, data: UpdateCarDto) { return this.prisma.cars.update({ where: { id }, data, include: this._include(), }); } ``` no corpo da requição eu receberia o id do usuário, id do carro além das alterações feitas, como seria essa comparação das alterações? > "ALTEROU A PLACA DE **XYZET5** PARA **ZBR3DA** E FILIAL DE **SÃO PAULO** PARA **RIO DE JANEIRO**".
Nunca usei prisma! Nisso não sei como ajudar
Sem problemas, a intenção valeu demais. Obrigado Uriel! 🤝
me intrometendo na conversa rs, mas assim acho que fazer algo genérico vai acabar dando mais trabalho é melhor vc criar um relacionamento direto e fazer ```ts return this.prisma.cars.update({ where: { id }, data: { ...data, historico: { push: { acao: 'ALTEROU A PLACA DE XYZET5 PARA ZBR3DA E FILIAL DE SÃO PAULO PARA RIO DE JANEIRO', usuario: usuario.id, ... }, } }, include: this._include(), }); ```
Não seria seguro mandar esse id no corpo da requisição, o usuário poderia bater na rota ou coisa do tipo e inserir o id de outro usuário :/
só usei assim como exemplo msm, o correto seria vc pegar qual o id do usuário logado e tal... uma coisa que não comentei tbm, como esse update está bem genérico você teria que fazer alguma lógica manual mesmo, pra poder ver quais são as alterações. E acredito que não precisa registrar tudo que for alterado, mas apenas o que for de fato importante para você
Muito obrigado amigo 🫂, voce também me ajudou bastante. O Paulo propôs uma solução, vou colcoar em prática e trazer as resoluções aqui, e quem sabe, um repository para ajudar outras pessoas