diff --git a/.github/workflows/run-arch-tests.yml b/.github/workflows/run-arch-tests.yml new file mode 100644 index 0000000..70c0251 --- /dev/null +++ b/.github/workflows/run-arch-tests.yml @@ -0,0 +1,20 @@ +name: Run Archtecture Tests + +on: [push] + +jobs: + run-arch-tests: + name: Run Archtecture Tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - run: npm ci + + - run: npm run test:arch \ No newline at end of file diff --git a/README.md b/README.md index 333c8a2..971161d 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,21 @@ O npm já vem por padrão na instalação do Node. **Acessando o PGAdmin:** Com o Docker em execução, você pode acessar o PGAdmin localmente. No seu navegador, vá até http://localhost:7070. A página pedirá um e-mail e senha, que estão configurados no arquivo .env do seu projeto. -**Configurando o Banco no PGAdmin:** Após fazer login, será necessário adicionar um novo servidor no PGAdmin. No campo "Connection" da nova configuração de servidor, no campo Host name/address, insira o nome do serviço do PostgreSQL definido no docker-compose.yml, que neste caso é db. Use as demais informações de conexão (usuário, senha, etc.) conforme descritas no arquivo .env. +**Configurando o Banco no PGAdmin:** Após fazer login, será necessário adicionar um novo servidor no PGAdmin. Na aba "Connection" da nova configuração de servidor, no campo Host name/address, insira o nome do serviço do PostgreSQL definido no docker-compose.yml, que neste caso é db. Use as demais informações de conexão (usuário, senha, etc.) conforme descritas no arquivo .env. + +## Estrutura do Projeto + +O projeto segue uma arquitetura limpa (Clean Architecture), dividindo responsabilidades em camadas bem definidas: + +- **Domain**: Contém as regras de negócio, entidades principais e casos de uso. +- **Delivery**: Responsável pela interface com o mundo externo (ex.: APIs REST). +- **Infrastructure**: Contém implementações específicas de banco de dados, serviços externos, etc. ## Testes automatizados -A aplicação possui testes automatizados de unidade e de integração para ter uma maior segurança no código e adequação aos requisitos. +A aplicação possui testes automatizados de unidade, integração e arquitetura para garantir maior segurança no código e adequação aos requisitos. + +### Testes de unidade Para executar os testes de unidade, rode: ```sh @@ -102,6 +112,7 @@ npm run test:watch Assim, sempre que houver uma atualização no código, os testes de unidade vão ser rodados novamente de forma automática. +### Testes de integração (e2e) Se quiser rodar os testes integrados (e2e), pode rodar: @@ -115,8 +126,34 @@ Da mesma forma, para rodar eles de forma automática sempre que houver alteraç npm run test:e2e:watch ``` -Pra finalizar, se quiser rodar todos os testes (unitários e de integração), rode: +### Testes de arquitetura + +Os testes de arquitetura verificam se o código segue os padrões e boas práticas definidos para o projeto. Para rodar esses testes, execute: + +```sh +npm run test:arch +``` + +Se quiser rodar os testes de arquitetura de forma contínua enquanto desenvolve, use: + +```sh +npm run test:arch:watch +``` + +### Rodando todos os testes + +Para rodar todos os testes (unitários, de integração e de arquitetura), execute: ```sh npm run test:all -``` \ No newline at end of file +``` + +## Contribuindo + +Contribuições são bem-vindas! Siga os passos abaixo para contribuir: + +1. Crie uma issue ou escolha uma issue existente do projeto. +1. Crie uma branch para sua feature ou correção de bug, baseado na issue. +2. Faça commit das suas alterações. +3. Envie para o repositório remoto. +4. Abra um Pull Request para branch "dev". diff --git a/dev-scripts/EXESQL.bat b/dev-scripts/EXESQL.bat new file mode 100644 index 0000000..04e56e4 --- /dev/null +++ b/dev-scripts/EXESQL.bat @@ -0,0 +1,28 @@ +@echo off +:: ===== CONFIGURAÇÃO ===== +set "PGUSER=postgres" +set "PGPASSWORD=alterar_para_senha_do_bd" +set "PGDB=bdchecklist" +set "PGHOST=localhost" +set "SQLFILE=InserirDadosTeste.sql" + +:: ===== VARIÁVEIS DE AMBIENTE ===== +set PGCLIENTENCODING=UTF8 + +echo. +echo =================================================== +echo INICIANDO SCRIPT DE POPULACAO DE BANCO DE DADOS +echo =================================================== +echo. +echo Inserindo dados de teste no banco '%PGDB%'... + +:: Executa o arquivo .sql usando psql +psql -U %PGUSER% -h %PGHOST% -d %PGDB% -f %SQLFILE% + +echo. +echo =================================================== +echo SCRIPT FINALIZADO +echo =================================================== +echo. +echo Pressione qualquer tecla para sair... +pause >nul diff --git a/dev-scripts/EXESQL.sh b/dev-scripts/EXESQL.sh new file mode 100755 index 0000000..dcf6e18 --- /dev/null +++ b/dev-scripts/EXESQL.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# ===== CONFIGURAÇÃO ===== +PGUSER="postgres" +PGPASSWORD="postgres" +PGDB="lgpd_checklist_local" +PGHOST="localhost" +SQLFILE="InserirDadosTeste.sql" + +# ===== VARIÁVEIS DE AMBIENTE ===== +export PGPASSWORD +export PGCLIENTENCODING=UTF8 + +# ===== EXECUÇÃO ===== +echo +echo "===================================================" +echo " INICIANDO SCRIPT DE POPULACAO DE BANCO DE DADOS" +echo "===================================================" +echo +echo "Inserindo dados de teste no banco '$PGDB'..." + +psql -U "$PGUSER" -h "$PGHOST" -d "$PGDB" -f "$SQLFILE" + +echo +echo "===================================================" +echo " SCRIPT FINALIZADO" +echo "===================================================" +echo + +read -n 1 -s -r -p "Pressione qualquer tecla para sair..." +echo diff --git a/dev-scripts/InserirDadosTeste.sql b/dev-scripts/InserirDadosTeste.sql new file mode 100644 index 0000000..b443680 --- /dev/null +++ b/dev-scripts/InserirDadosTeste.sql @@ -0,0 +1,300 @@ +TRUNCATE TABLE + "users", "sections", "laws", "devices", "items", "systems", "checklists", "checklist_items" +RESTART IDENTITY CASCADE; + +INSERT INTO sections (name) VALUES +('Sobre transparência de Dados (T)'), +('Sobre Consentimento do Titular (C)'), +('Sobre os Direitos do Titular (D)'), +('Sobre Segurança de Dados (S)'), +('Sobre Responsabilidade do Controlador (R)'), +('Acesso ao Dispositivo (A)'), +('Segurança Física (SF)'); + +INSERT INTO laws (name) VALUES +('Lei Geral de Proteção de Dados (LGPD) - Brasil'), +('General Data Protection Regulation (GDPR) - União Europeia'), +('California Consumer Privacy Act (CCPA / CPRA) - EUA'), +('Privacy Act 1988 - Austrália'), +('American Data Privacy and Protection Act (ADPPA) - Proposta EUA'); + +INSERT INTO devices (name) VALUES +('Câmera de Segurança IP'), +('Sensor de Presença Infravermelho'), +('Controladora de Acesso Biométrico'), +('Termostato Inteligente'), +('Detector de Fumaça Endereçável'), +('Smartwatch com Monitoramento de Saúde'), +('Assistente de Voz Doméstico'), +('Fechadura Conectada à Nuvem'), +('Medidor de Energia Inteligente'), +('Carro Conectado com Telemetria'), +('Geladeira Inteligente com Câmera'), +('Drone de Vigilância 4K'), +('Tag de Localização Bluetooth'), +('Balança Inteligente Corporal'), +('Lâmpada Inteligente RGB'), +('Tomada Inteligente WiFi'), +('Sensor de Abertura de Porta/Janela'), +('Cadeado Inteligente com Bluetooth'), +('Robô Aspirador com Mapeamento'), +('Estação Meteorológica Conectada'), +('Monitor de Qualidade do Ar Interno'), +('Bomba de Infusão Hospitalar IoT'), +('Sensor de Umidade do Solo para Agricultura'), +('Gateway de Rede LoRaWAN'), +('Etiqueta Eletrônica de Prateleira'), +('Terminal de Ponto de Venda (PoS) Móvel'), +('Impressora 3D Conectada em Rede'), +('Sistema de Irrigação Inteligente'), +('Coleira de Rastreamento GPS para Pets'), +('Painel Solar com Monitoramento Remoto'), +('Sensor de Vibração Industrial'), +('Atuador Linear Elétrico Conectado'), +('Medidor de pH para Aquicultura'), +('Câmera Termográfica para Manutenção'), +('Dispensador Automático de Medicamentos'), +('Sistema de Som Multi-room WiFi'), +('Projetor Inteligente 4K'), +('Feeder Automático para Animais de Estimação'), +('Monitor Cardíaco Implantável'), +('Sensor de Nível para Tanques de Combustível'); + +INSERT INTO users (name, office, email, password) VALUES +('Juliana Alves', 'Data Protection Officer (DPO)', 'juliana.dpo@empresa.com', 'dpo123'), +('Ricardo Mendes', 'Arquiteto de Soluções IoT', 'ricardo.iot@empresa.com', 'iot456'), +('Sofia Costa', 'Advogada de Privacidade', 'sofia.legal@empresa.com', 'legal789'), +('Lucas Ferreira', 'Engenheiro de Segurança', 'lucas.sec@empresa.com', 'seguranca10'), +('Beatriz Rocha', 'Product Owner', 'beatriz.po@empresa.com', 'produto11'), +('Fernando Lima', 'Analista de GRC', 'fernando.grc@empresa.com', 'grc2025'), +('Mariana Gomes', 'Desenvolvedora Backend Sênior', 'mariana.dev@empresa.com', 'devbackend1'), +('Pedro Almeida', 'Gerente de Produto', 'pedro.pm@empresa.com', 'produtomaster'), +('Clara Bastos', 'Analista de Qualidade (QA)', 'clara.qa@empresa.com', 'qatester'), +('Rafael Martins', 'Arquiteto de Nuvem', 'rafael.cloud@empresa.com', 'cloudarch'), +('Matheus Frej Lemos Cavalcanti', 'Engenheiro de Software', 'matheusfrej@gmail.com', '$2a$11$rh/s23beBrESrkYy3AkOnOeeEGNLOk5nrRVjOtgE0rk7sATPvOvfq'), +('Cesar Yuri e Silva Santos', 'Engenheiro de Computação', 'cesar.yuri@ufrpe.br', '$2a$11$UHBHGq8oYBoQxibaLhpPy.R7EqVnDPFlkOgq0dDEYWl.PauyoJyny'), +('Amanda Oliveira', 'Analista de Dados', 'amanda.data@empresa.com', 'data_pass'), +('Bruno Santos', 'Desenvolvedor Frontend', 'bruno.front@empresa.com', 'front_pass'), +('Camila Pereira', 'UX/UI Designer', 'camila.ux@empresa.com', 'design_pass'), +('Daniel Costa', 'DevOps Engineer', 'daniel.devops@empresa.com', 'devops_pass'), +('Eduarda Ribeiro', 'Scrum Master', 'eduarda.scrum@empresa.com', 'scrum_pass'), +('Fábio Azevedo', 'Gerente de Projetos', 'fabio.pm@empresa.com', 'project_pass'), +('Gabriela Lima', 'Analista de Suporte', 'gabriela.support@empresa.com', 'support_pass'), +('Heitor Barbosa', 'DBA', 'heitor.dba@empresa.com', 'database_pass'), +('Isabela Castro', 'Analista de Marketing', 'isabela.mkt@empresa.com', 'mkt_pass'), +('João Vitor Melo', 'Estagiário de Desenvolvimento', 'joao.intern@empresa.com', 'intern_pass'), +('Larissa Dias', 'Recrutadora de TI', 'larissa.hr@empresa.com', 'hr_pass'), +('Márcio Nunes', 'Diretor de Tecnologia (CTO)', 'marcio.cto@empresa.com', 'cto_pass'), +('Natália Correia', 'Analista de BI', 'natalia.bi@empresa.com', 'bi_pass'), +('Otávio Pinto', 'Analista de Redes', 'otavio.net@empresa.com', 'net_pass'), +('Patrícia Farias', 'Analista Financeiro', 'patricia.fin@empresa.com', 'fin_pass'), +('Quintino Barros', 'Consultor de Segurança', 'quintino.con@empresa.com', 'con_pass'), +('Renata Tavares', 'Coordenadora de Operações', 'renata.ops@empresa.com', 'ops_pass'), +('Sérgio Viana', 'Engenheiro de Dados', 'sergio.de@empresa.com', 'dataeng_pass'), +('Thiago Andrade', 'Cientista de Dados', 'thiago.ds@empresa.com', 'science_pass'), +('Úrsula Magalhães', 'Gerente de Contas', 'ursula.acc@empresa.com', 'account_pass'), +('Vitor Hugo Paes', 'Arquiteto de Dados', 'vitor.da@empresa.com', 'arch_pass'), +('William Bonner', 'Analista de Comunicação', 'william.comm@empresa.com', 'comm_pass'), +('Xavier Nogueira', 'Especialista em Redes Sociais', 'xavier.social@empresa.com', 'social_pass'), +('Yasmin Fernandes', 'Analista de SEO', 'yasmin.seo@empresa.com', 'seo_pass'), +('Ziraldo Alves', 'Ilustrador', 'ziraldo.art@empresa.com', 'art_pass'), +('Aline Barros', 'Cantora Gospel', 'aline.gospel@empresa.com', 'gospel_pass'), +('Bento Ribeiro', 'Comediante', 'bento.humor@empresa.com', 'humor_pass'), +('Cauã Reymond', 'Ator', 'caua.actor@empresa.com', 'actor_pass'); + +INSERT INTO items (code, item_desc, recommendations, is_mandatory, "section_id") VALUES +('T-01', 'As finalidades de tratamento de dados foram definidas na organização?', 'Crie um documento (Política de Privacidade) descrevendo os objetivos e a forma de utilização dos dados.', true, 1), +('T-02', 'O tratamento de dados pessoais é realizado de acordo com uma base legal?', 'Na Política de Privacidade, explique a base legal do tratamento (ex: consentimento, obrigação legal). Consulte o Art. 7 da LGPD para a lista completa.', true, 1), +('T-03', 'O sistema informa ao titular sobre as finalidades de tratamento de dados pessoais?', 'O sistema deve disponibilizar a Política de Privacidade ou Termos de Consentimento para o titular.', true, 1), +('T-04', 'O sistema realiza o tratamento de dados em conformidade com a finalidade apresentada ao titular?', 'Revise o consentimento e implemente as alterações necessárias para garantir a conformidade.', true, 1), +('T-05', 'O sistema informa ao titular a forma e duração do tratamento dos seus dados de maneira gratuita e acessível?', 'Descreva na Política de Privacidade a forma e a duração do tratamento dos dados, mantendo a informação sempre acessível.', true, 1), +('T-06', 'O sistema permite o titular consultar sobre a integralidade dos seus dados pessoais de maneira gratuita e acessível?', 'Crie um canal de comunicação (e-mail, página de contato) para que o titular possa requisitar seus dados de forma gratuita.', true, 1), +('T-07', 'O sistema armazena com exatidão e clareza os dados pessoais coletados dos titulares?', 'Durante a coleta, garanta a correção dos dados. Use validação de campos nos formulários (CPF, CEP, etc).', true, 1), +('T-08', 'O sistema mantém atualizados os dados pessoais, conforme necessário para a finalidade de seu tratamento?', 'Crie um processo para mapear e revisar os dados. Se descobrir dados incorretos, corrija ou apague-os rapidamente.', true, 1), +('T-09', 'O sistema disponibiliza ao titular as informações sobre o tratamento e a identidade do controlador?', 'Disponibilize na Política de Privacidade informações claras sobre: finalidades, identidade do controlador, dados envolvidos, base legal, etc.', true, 1), +('T-10', 'A organização realiza o tratamento baseado no legítimo interesse, de forma adequada com a lei?', 'A empresa deve realizar uma análise (LIA) para verificar se a hipótese se enquadra na base legal do legítimo interesse.', true, 1), +('T-11', 'O sistema mantém registros das operações de tratamento de dados, especialmente quando baseado em legítimo interesse?', 'Armazene no banco de dados os logs das operações de tratamento de dados para possíveis comprovações legais.', true, 1), +('T-12', 'O sistema informa ao titular e autoridades sobre o tratamento, quando baseado em legítimo interesse?', 'Elabore relatórios das operações de tratamento de dados e disponibilize-os para o titular e autoridades (ANPD) quando requisitado.', true, 1), +('T-13', 'O sistema realiza o tratamento de forma que os dados não possam ser conectados a um titular, quando requisitado?', 'Analise quais dados devem ser anonimizados ou pseudonimizados. Ex: uma versão dos dados sem revelar a identidade real do titular.', true, 1), +('T-14', 'O sistema informa ao titular sobre a existência de tratamento dos seus dados, antes da coleta?', 'Disponibilize ao titular, com destaque, avisos sobre os dados que serão coletados e solicite seu consentimento.', true, 1), +('C-01', 'O sistema permite que o titular forneça seu consentimento de forma autônoma e clara?', 'Use linguagem clara, evite caixas pré-marcadas e ofereça uma opção para negar ou retirar o consentimento sem prejuízos.', true, 2), +('C-02', 'O sistema solicita consentimento específico para compartilhar dados com outros controladores?', 'Seja transparente sobre o compartilhamento de dados com terceiros, informando o titular de forma clara no consentimento.', true, 2), +('C-03', 'O sistema armazena o consentimento dado pelo titular para comprovações legais?', 'Armazene no banco de dados os registros (logs) de consentimento do titular para comprovação.', true, 2), +('C-04', 'O sistema permite ao titular meios para recusar ou retirar o consentimento sem prejuízo?', 'O titular deve ter acesso ao seu termo de consentimento ou contrato com a opção de negar ou revogar o tratamento de dados.', true, 2), +('C-05', 'O tratamento de dados de crianças e adolescentes tem consentimento específico dos pais ou responsável legal?', 'Certifique que apenas o responsável legítimo pode dar o consentimento e atualizá-lo. Considere usar autenticação do titular.', true, 2), +('C-06', 'O sistema informa o titular sobre mudanças de finalidade, permitindo a revogação do consentimento?', 'Sempre que alterar a finalidade, informe o titular e solicite novo consentimento, com a opção de negar a alteração.', true, 2), +('C-07', 'A declaração de consentimento é inteligível, acessível e não contém termos abusivos?', 'O Termo de Consentimento deve ser bem escrito, com linguagem de fácil compreensão e respeitando os direitos do titular.', true, 2), +('C-08', 'O sistema informa o titular de dados sobre todos os seus direitos no consentimento?', 'Informe no Termo de Consentimento e na Política de Privacidade a lista de direitos que os titulares de dados possuem.', true, 2), +('D-01', 'O sistema armazena os dados pessoais em formato que facilite o acesso do titular?', 'Armazene os dados em formatos estruturados e comuns (CSV, JSON, XML) para que possam ser facilmente extraídos.', true, 3), +('D-02', 'O sistema fornece ao titular meios para registrar reclamações sobre o tratamento de seus dados?', 'Disponibilize o contato do DPO ou um canal exclusivo para reclamações na política de privacidade.', true, 3), +('D-03', 'O sistema fornece a cópia eletrônica integral dos dados pessoais para o titular?', 'Transmitir diretamente os dados solicitados ao titular ou fornecer acesso a uma ferramenta que permita a extração pelo próprio usuário.', true, 3), +('D-04', 'O sistema permite que o titular atualize seus dados pessoais?', 'Crie um mecanismo para que o próprio titular possa atualizar seus dados pessoais, como uma página de perfil.', true, 3), +('D-05', 'O sistema utiliza apenas dados relevantes e adequados à finalidade (Minimização)?', 'Realize a minimização dos dados, utilizando apenas o que for relevante. Considere anonimizar ou eliminar dados desnecessários.', true, 3), +('D-06', 'O sistema permite a portabilidade de dados a outro controlador mediante requisição do titular?', 'Os dados devem ser armazenados em formato legível por máquina para permitir a exportação quando o titular requisitar.', true, 3), +('D-07', 'O sistema permite excluir os dados pessoais do titular, mediante a sua requisição?', 'Crie um mecanismo para que o titular possa solicitar a remoção permanente de seus dados pessoais.', true, 3), +('D-08', 'O sistema remove os dados pessoais do titular após o término de seu tratamento?', 'É necessário excluir os dados pessoais do titular após o cumprimento da finalidade, no prazo estabelecido.', true, 3), +('D-09', 'O sistema fornece informações sobre entidades públicas e privadas, caso tenha realizado compartilhamento?', 'Armazene as informações das entidades e das operações de tratamento para comprovações legais e disponibilize quando requisitado.', true, 3), +('D-10', 'O sistema informa o titular sobre a possibilidade de não fornecer consentimento e as consequências?', 'Deve fornecer no Termo de Consentimento a possibilidade de recusar o tratamento, e as consequências da negativa.', true, 3), +('D-11', 'O sistema informa quando o tratamento de dados é condição para fornecimento de produto ou serviço?', 'Deve ficar claro no momento do consentimento do titular os objetivos e pré-requisitos do tratamento de dados.', true, 3), +('D-12', 'O sistema permite aos titulares, o direito de se opor a um tratamento de dados de forma fácil?', 'É necessário fornecer um mecanismo para que o titular consiga retirar seu consentimento. Caso retirado, os dados não poderão ser mais processados.', true, 3), +('D-13', 'O sistema informa ao titular, as razões do não exercício imediato de seus direitos?', 'Quando não for possível atender imediatamente, deve-se comunicar o motivo (de fato ou de direito) ou indicar o agente de tratamento responsável.', true, 3), +('D-14', 'O sistema informa o titular quando uma decisão é tomada com base no tratamento automatizado?', 'Disponibilize para o titular as informações das decisões automatizadas que serão realizadas. Forneça o registro se requisitado.', true, 3), +('D-15', 'O sistema fornece a opção do titular contestar ou solicitar revisão da decisão automatizada?', 'Caso seja requisitado, deverá revisar a decisão automatizada contestada, podendo interromper o tratamento.', true, 3), +('S-01', 'O sistema realiza o tratamento de dados de forma segura, incluindo proteção contra acesso não autorizado?', 'Garanta a segurança no tratamento, possibilitando apenas indivíduos autorizados a ler, modificar ou excluir dados (Controle de Acesso).', true, 4), +('S-02', 'O sistema respeita as normas de transferência internacional de dados para países com proteção adequada?', 'Verifique se o país de destino possui nível de proteção de dados adequado à LGPD e se a ANPD autoriza tais operações.', true, 4), +('S-03', 'O sistema informa o titular sobre as transferências internacionais de seus dados pessoais?', 'Informar no documento da Política de Privacidade e no Termo de Consentimento, sobre a realização de transferências de dados.', true, 4), +('S-04', 'O sistema utiliza mecanismos para prevenir a ocorrência de danos, destruição ou perda de dados?', 'Implemente boas práticas como backup de dados, armazenamento em nuvem e outras ações para proteger as informações.', true, 4), +('S-05', 'O sistema utiliza medidas de proteção adequadas para dados pessoais sensíveis?', 'Implemente uma camada adicional de segurança, como criptografia e controle de acesso restrito, para dados sensíveis.', true, 4), +('R-01', 'A organização indica um oficial de proteção de dados (DPO) encarregado pelo tratamento de dados?', 'A organização deve indicar um DPO para atuar como canal de comunicação com os titulares e a ANPD.', true, 5), +('R-02', 'A organização divulga publicamente a identidade e as informações de contato do DPO?', 'A informação deve estar de forma clara na Política de Privacidade para que os titulares e a autoridade nacional tenham acesso facilitado.', true, 5), +('R-03', 'A organização comunica à ANPD e ao titular a ocorrência de incidente de segurança relevante?', 'Comunique incidentes à ANPD em até 2 dias úteis da ciência. O conteúdo deve seguir o Art. 48 da LGPD.', true, 5), +('R-04', 'A organização realiza avaliação de impacto à proteção de dados (AIPD) para tratamentos de alto risco?', 'A organização deve realizar uma AIPD quando existir elevado risco no tratamento. A avaliação deve ser sempre revisada.', true, 5), +('R-05', 'A organização realiza e disponibiliza o relatório de impacto à proteção de dados (RIPD) à ANPD?', 'O relatório deve conter a descrição dos dados, metodologia, garantias de segurança e análise de mitigação de riscos.', true, 5), +('S-06', 'O sistema utiliza boas práticas como privacy by design?', 'Como boa prática para o desenvolvimento e adequação do sistema, implementar a metodologia privacy by design.', false, 4), +('S-07', 'O sistema realiza o mapeamento dos dados pessoais e mantém em segurança?', 'Organizar os locais onde os dados são coletados e armazenados.', false, 4), +('S-08', 'O sistema fornece a confidencialidade, usando medidas técnicas apropriadas?', 'Realizar a proteção contra acesso não autorizado no uso de dados, como autenticação e controle de acesso.', false, 4), +('S-09', 'O sistema fornece a integridade dos dados pessoais, impedindo modificações não autorizadas?', 'Verificar a integridade dos dados (ex: checksums) durante a coleta e no tratamento dos dados pessoais.', false, 4), +('S-10', 'O sistema mantém registros de auditorias de conformidades e disponibiliza ao titular, se requisitado?', 'Uma solução seria documentar os dados das auditorias realizadas para fornecer ao titular.', false, 4), +('S-11', 'A organização possui um plano de correção a incidentes de privacidade e segurança?', 'A organização deve documentar um plano para correção de falhas ou incidentes de segurança.', false, 4), +('S-12', 'O sistema possui certificação ou selos para comprovar a conformidade com as normas de proteção de dados?', 'É recomendável que o sistema tenha uma certificação (ex: ISO 27001, ISO 27701) que comprove a aderência à lei.', false, 4), +('S-13', 'O dispositivo possui certificação para comprovar os padrões de qualidade (ex: Anatel)?', 'Certificações comprovam que o dispositivo possui níveis de qualidade e segurança determinados pela regulamentação.', false, 4), +('S-14', 'O dispositivo é seguro contra ataques de força bruta ou outras tentativas abusivas de login?', 'Uma solução é criar mecanismo para bloquear os utilizadores após várias tentativas inválidas de acesso.', false, 4), +('S-15', 'O dispositivo possui mecanismos contra ataques conhecidos (ex: buffer overflow, DDoS)?', 'Realizar uma bateria de testes de ataques mais conhecidos atualmente como forma de prevenção.', false, 4), +('S-16', 'Os dispositivos utilizam técnicas de proteção para realizar uma comunicação segura entre si?', 'Ao compartilhar os dados entre os dispositivos, é necessário que técnicas de proteção sejam implementadas.', false, 4), +('S-17', 'As senhas ficam encriptadas na base de dados do dispositivo?', 'Se as senhas salvas estiverem criptografadas, se tornam mais seguras contra acesso indevido ao dispositivo.', false, 4), +('S-18', 'O dispositivo encaminha os dados devidamente para o seu destino correto?', 'Realizar verificações para identificar se os dados estão sendo encaminhados apenas para seu devido destino.', false, 4), +('S-19', 'O dispositivo utiliza protocolos como TLS para criptografar as comunicações?', 'Os protocolos TLS servem para criptografar as comunicações, proporcionando segurança na troca de dados.', false, 4), +('S-20', 'A organização cria mecanismos de segurança para os dispositivos com pouco processamento?', 'Uma alternativa é a utilização de gateways, para realizar o tratamento que o dispositivo restrito não consegue.', false, 4), +('S-21', 'O dispositivo recebe atualizações de segurança?', 'As atualizações de segurança corrigem falhas e diminuem os riscos contra invasões.', false, 4), +('S-22', 'O dispositivo encripta os dados no armazenamento para evitar a identificação dos dados pessoais?', 'Ao armazenar os dados com criptografia, eles ficarão mais protegidos em caso de violação.', false, 4), +('R-06', 'Existe um profissional de segurança fiscalizando atividades suspeitas ou incomuns?', 'Ter um profissional fiscalizando todo o processo diminuirá as chances de um incidente de dados.', false, 5), +('S-23', 'Os dispositivos são alocados em uma VLAN específica, separando-se de outras redes?', 'Realizar o gerenciamento da rede, separando o acesso de usuários/visitantes da rede dos dispositivos.', false, 4), +('S-24', 'A organização recebe notificação quando a senha do dispositivo é trocada?', 'A notificação permite que a organização tome medidas protetivas imediatamente em caso de acesso não autorizado.', false, 4), +('S-25', 'A organização recebe notificações em caso de acessos não autorizados?', 'Criar mecanismos para notificações se ocorrer acessos indevidos, para agir imediatamente em caso de invasões.', false, 4), +('R-07', 'A organização registra as atividades realizadas pelo DPO para comprovações legais?', 'Armazene e documente o histórico de atividades do encarregado DPO.', false, 5), +('R-08', 'A organização realiza capacitação para seus funcionários sobre a aderência à LGPD?', 'Capacite os funcionários sobre as informações que a LGPD determina através de treinamentos, cursos e palestras.', false, 5), +('R-09', 'São realizadas capacitações para os funcionários que utilizam os dispositivos?', 'Realizar capacitações com os funcionários para garantir a qualidade e eficiência de todo o processo.', false, 5), +('A-01', 'A organização fornece credenciais diferentes para cada utilizador?', 'Deve ser realizado o controle de acesso para cada utilizador, criando uma lista de pessoas autorizadas.', false, 6), +('A-02', 'São atribuídas senhas diferentes a cada dispositivo para fortalecer a segurança?', 'Utilizar a mesma senha enfraquece a segurança. O ideal é ter senhas diferentes, talvez geradas por um algoritmo.', false, 6), +('A-03', 'Houve mudança da senha padrão do dispositivo para evitar acesso não autorizado?', 'É de suma importância a mudança imediata da senha padrão do dispositivo, que é facilmente encontrada no manual.', false, 6), +('A-04', 'O dispositivo garante que a senha seja forte e segura?', 'Para maior proteção, é fundamental senhas fortes (letras maiúsculas, minúsculas, números e símbolos).', false, 6), +('A-05', 'São registradas falhas ou possíveis suspeitas de vulnerabilidade?', 'Realizar registros de todas as falhas e suspeitas reais. Possuir esta documentação ajuda na organização e prevenção.', false, 6), +('SF-01', 'O dispositivo possui apenas portas em funcionamento, não permitindo portas desnecessárias?', 'Todas as portas que não estão em uso, deverão ser desativadas. Exemplos de portas: RJ-45 e USB.', false, 7), +('SF-02', 'O dispositivo não possibilita o acesso físico de pessoas não autorizadas?', 'Analisar o ambiente no qual o dispositivo se encontra, analisando possíveis riscos que o dispositivo poderá sofrer.', false, 7), +('SF-03', 'O dispositivo é protegido contra tentativas de ser resetado para configuração de fábrica?', 'Apenas pessoas autorizadas devem ter acesso físico ao botão de reset. Se não for restrito, uma solução é a blindagem do equipamento.', false, 7), +('SF-04', 'O dispositivo é resistente para o ambiente que está alocado?', 'Realizar uma análise das condições ambientais (temperatura, umidade), identificando se afetarão o dispositivo.', false, 7), +('SF-05', 'A manutenção do dispositivo é realizada conforme recomendação do fornecedor?', 'O ideal é que as manutenções sejam realizadas nos intervalos e de acordo com as recomendações do fornecedor.', false, 7), +('SF-06', 'A manutenção e consertos dos dispositivos são realizados por uma equipe autorizada?', 'Para a confiabilidade do procedimento, é de grande importância ser realizado por uma equipe autorizada.', false, 7), +('SF-07', 'São registradas todas as manutenções preventivas?', 'A manutenção preventiva é a garantia do bom funcionamento e o registro se torna a comprovação e organização do procedimento.', false, 7); + +INSERT INTO systems (name, description, user_id) VALUES +('Plataforma de Videomonitoramento', 'Serviço de nuvem para câmeras com IA', 1), +('App de Saúde "Vitality"', 'Centraliza dados de smartwatches', 2), +('Assistente "Home Voice"', 'Backend para automação residencial', 3), +('Controle de Acesso Corp', 'Gerenciamento de fechaduras biométricas', 4), +('Rede de Medição Inteligente', 'Coleta de dados de consumo elétrico', 5), +('Gestão de Frotas "OnTrack"', 'Plataforma de telemetria veicular', 6), +('App "SmartFridge"', 'Gerenciamento de geladeiras inteligentes', 7), +('Sistema "AeroView" de Drones', 'Controle e gravação de drones', 8), +('Serviço "FindMe" de Localização', 'Rastreamento de pertences com tags', 9), +('Plataforma "BioScale" de Análise', 'Nuvem para dados de balanças', 10), +('Sistema de Irrigação "AquaFarm"', 'Controle remoto de irrigação', 11), +('Monitor de Qualidade do Ar "AirPure"', 'Coleta de dados de poluição', 12), +('Plataforma de Ponto de Venda "SellFast"', 'Sistema de pagamentos móvel', 13), +('Gateway de Rede Industrial', 'Conexão de sensores de chão de fábrica', 14), +('Sistema de Gerenciamento de Energia Solar', 'Monitoramento de painéis solares', 15), +('Plataforma de Educação a Distância', 'Ambiente virtual de aprendizagem', 16), +('Software de Impressão 3D em Nuvem', 'Gerenciamento de impressoras 3D', 17), +('Controle de Iluminação Pública', 'Sistema de gerenciamento de lâmpadas LED', 18), +('Rastreador de Atividade para Pets', 'Monitoramento de saúde de animais', 19), +('Estação Meteorológica Pessoal', 'Coleta de dados climáticos locais', 20), +('Gerenciador de Senhas Corporativo', 'Cofre de senhas para equipes', 21), +('Plataforma de Análise de Sentimento', 'Análise de mídias sociais', 22), +('Sistema de Reserva de Salas', 'Agendamento de salas de reunião', 23), +('Controle de Estoque Automatizado', 'Gerenciamento de inventário com RFID', 24), +('Plataforma de Telemedicina', 'Consultas médicas online', 25), +('Sistema de Notificação de Emergência', 'Alerta para desastres naturais', 26), +('Gerenciador de Cadeia de Suprimentos', 'Rastreamento de produtos da origem ao fim', 27), +('Plataforma de Crowdfunding', 'Arrecadação de fundos para projetos', 28), +('Software de Simulação de Engenharia', 'Cálculos estruturais em nuvem', 29), +('Sistema de Gerenciamento de Resíduos', 'Otimização de rotas de coleta', 30), +('Plataforma de Gestão de Condomínio', 'Comunicação e reservas para condomínios', 31), +('App de Fidelidade para Varejo', 'Pontos e recompensas para clientes', 32), +('Sistema de Monitoramento de Pacientes', 'Coleta de sinais vitais em hospitais', 33), +('Plataforma de Streaming de Música', 'Serviço de áudio on-demand', 34), +('App de Mobilidade Urbana', 'Serviço de transporte por aplicativo', 35), +('Sistema de Gestão Agrícola', 'Monitoramento de safras e maquinário', 36), +('Plataforma de e-Commerce B2B', 'Vendas online para empresas', 37), +('Software de Análise de Risco de Crédito', 'Avaliação de crédito automatizada', 38), +('App de Meditação e Mindfulness', 'Conteúdo guiado para bem-estar', 39), +('Sistema de Gerenciamento de Frotas de Patinetes', 'Controle e manutenção de patinetes elétricos', 40); + +INSERT INTO checklists (user_id, system_id) VALUES +(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10), +(11, 11), (12, 12), (13, 13), (14, 14), (15, 15), (16, 16), (17, 17), (18, 18), (19, 19), (20, 20), +(21, 21), (22, 22), (23, 23), (24, 24), (25, 25), (26, 26), (27, 27), (28, 28), (29, 29), (30, 30), +(31, 31), (32, 32), (33, 33), (34, 34), (35, 35), (36, 36), (37, 37), (38, 38), (39, 39), (40, 40); + +INSERT INTO checklist_items (checklist_id, item_id, answer, severity_degree, user_comment) VALUES +(1, 1, 'Conforme', 'Baixo', 'Política de privacidade definida e documentada.'), +(2, 2, 'Não Conforme', 'Alto', 'Base legal para coleta de dados de geolocalização não está clara.'), +(3, 3, 'Conforme', 'Nenhum', 'Link para política de privacidade visível na tela de login.'), +(4, 4, 'Conforme', 'Nenhum', 'Dados utilizados apenas para a finalidade de automação residencial.'), +(5, 5, 'Não Conforme', 'Médio', 'Duração do tratamento não especificada nos termos de uso.'), +(6, 6, 'Em Andamento', 'Médio', 'Canal de comunicação implementado, mas com tempo de resposta alto.'), +(7, 7, 'Conforme', 'Nenhum', 'Validação de CPF e CEP implementada no formulário de cadastro.'), +(8, 8, 'Conforme', 'Baixo', 'Processo de atualização de dados via perfil do usuário funcional.'), +(9, 9, 'Não Conforme', 'Alto', 'Identidade do controlador não está clara na política de privacidade.'), +(10, 10, 'Conforme', 'Médio', 'Análise de legítimo interesse para notificações de marketing realizada e documentada.'), +(11, 11, 'Conforme', 'Baixo', 'Logs de operações de tratamento estão sendo armazenados.'), +(12, 12, 'Em Andamento', 'Baixo', 'Modelo de relatório para ANPD em desenvolvimento.'), +(13, 13, 'Conforme', 'Nenhum', 'Função de anonimização de dados implementada e testada.'), +(14, 14, 'Não Conforme', 'Médio', 'Aviso de coleta de cookies não é exibido na primeira visita.'), +(15, 15, 'Conforme', 'Nenhum', 'Checkbox de consentimento não vem pré-marcado.'), +(16, 16, 'Não Conforme', 'Alto', 'Compartilhamento de dados com parceiros sem consentimento específico.'), +(17, 17, 'Conforme', 'Nenhum', 'Registro de consentimento com timestamp armazenado com sucesso.'), +(18, 18, 'Conforme', 'Baixo', 'Link para revogar consentimento disponível no painel do usuário.'), +(19, 19, 'Não Conforme', 'Crítico', 'Não há verificação de idade ou consentimento parental para usuários menores.'), +(20, 20, 'Conforme', 'Baixo', 'Notificação por e-mail enviada ao alterar a política de privacidade.'), +(21, 21, 'Conforme', 'Nenhum', 'Termos de uso escritos em linguagem clara e acessível.'), +(22, 22, 'Conforme', 'Baixo', 'Seção "Seus Direitos" adicionada à política de privacidade.'), +(23, 23, 'Conforme', 'Nenhum', 'Exportação de dados do usuário em formato JSON implementada.'), +(24, 24, 'Conforme', 'Nenhum', 'Contato do DPO visível no site da empresa.'), +(25, 25, 'Em Andamento', 'Médio', 'Função de exportação de dados está em fase de testes.'), +(26, 26, 'Conforme', 'Nenhum', 'Usuário pode editar suas informações pessoais na área de perfil.'), +(27, 27, 'Conforme', 'Baixo', 'Coleta de dados limitada ao essencial para o funcionamento do app.'), +(28, 28, 'Não Conforme', 'Alto', 'Função de portabilidade para outro controlador não implementada.'), +(29, 29, 'Conforme', 'Nenhum', 'Função "deletar minha conta" remove os dados do banco.'), +(30, 30, 'Não Conforme', 'Médio', 'Dados permanecem no banco de dados mesmo após o término do contrato.'), +(31, 31, 'Conforme', 'Nenhum', 'Reclamações são direcionadas ao DPO.'), +(32, 32, 'Conforme', 'Baixo', 'Cópia integral dos dados é fornecida em formato CSV.'), +(33, 33, 'Conforme', 'Nenhum', 'Perfil do usuário permite atualização de dados.'), +(34, 34, 'Conforme', 'Nenhum', 'Apenas dados essenciais são coletados para a função.'), +(35, 35, 'Não Aplicável', 'Nenhum', 'Não há portabilidade para este tipo de sistema.'), +(36, 36, 'Conforme', 'Baixo', 'Função de exclusão está disponível.'), +(37, 37, 'Não Conforme', 'Médio', 'Dados não são removidos após o término do tratamento.'), +(38, 38, 'Conforme', 'Nenhum', 'Informações sobre compartilhamento estão na política.'), +(39, 39, 'Conforme', 'Nenhum', 'Consequências da negação do consentimento são informadas.'), +(40, 40, 'Conforme', 'Baixo', 'Informação sobre tratamento condicional é clara.'); + +INSERT INTO "_item_laws" ("A", "B") VALUES +(1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), +(11, 1), (12, 1), (13, 1), (14, 1), (15, 2), (16, 2), (17, 2), (18, 2), (19, 2), (20, 2), +(21, 2), (22, 2), (23, 3), (24, 3), (25, 3), (26, 3), (27, 3), (28, 3), (29, 3), (30, 3), +(31, 1), (32, 1), (33, 1), (34, 1), (35, 1), (36, 1), (37, 1), (38, 1), (39, 1), (40, 1), +(41, 1), (42, 1), (43, 1), (44, 1), (45, 1), (46, 1), (47, 1), (48, 1), (49, 1), (50, 1), +(51, 1), (52, 1), (53, 1), (54, 1), (55, 1), (56, 1), (57, 1), (58, 1), (59, 1), (60, 1), +(61, 1), (62, 1), (63, 1), (64, 1), (65, 1), (66, 1), (67, 1), (68, 1), (69, 1), (70, 1), +(71, 1), (72, 1), (73, 1), (74, 1), (75, 1), (76, 1), (77, 1); + +INSERT INTO "_checklist_laws" ("A", "B") VALUES +(1, 1), (2, 2), (3, 3), (4, 1), (5, 1), (6, 1), (7, 3), (8, 1), (9, 3), (10, 2), +(11, 1), (12, 1), (13, 3), (14, 1), (15, 1), (16, 2), (17, 1), (18, 1), (19, 1), (20, 4), +(21, 1), (22, 5), (23, 2), (24, 1), (25, 2), (26, 1), (27, 1), (28, 3), (29, 1), (30, 1), +(31, 1), (32, 3), (33, 2), (34, 3), (35, 1), (36, 1), (37, 3), (38, 1), (39, 2), (40, 1); + +INSERT INTO "_checklist_devices" ("A", "B") VALUES +(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10), +(11, 11), (12, 12), (13, 13), (14, 14), (15, 15), (16, 16), (17, 17), (18, 18), (19, 19), (20, 20), +(21, 21), (22, 22), (23, 23), (24, 24), (25, 25), (26, 26), (27, 27), (28, 28), (29, 29), (30, 30), +(31, 31), (32, 32), (33, 33), (34, 34), (35, 35), (36, 36), (37, 37), (38, 38), (39, 39), (40, 40); \ No newline at end of file diff --git a/dev-scripts/Itensdofrotendantigoteste.txt b/dev-scripts/Itensdofrotendantigoteste.txt new file mode 100644 index 0000000..ff6333a --- /dev/null +++ b/dev-scripts/Itensdofrotendantigoteste.txt @@ -0,0 +1,1050 @@ +('Matheus Frej Lemos Cavalcanti', 'Engenheiro de Software', 'matheusfrej@gmail.com', '$2a$11$rh/s23beBrESrkYy3AkOnOeeEGNLOk5nrRVjOtgE0rk7sATPvOvfq'), +('Cesar Yuri e Silva Santos', 'Engenheiro de Computação', 'cesar.yuri@ufrpe.br', '$2a$11$UHBHGq8oYBoQxibaLhpPy.R7EqVnDPFlkOgq0dDEYWl.PauyoJyny'); + + +import { ChecklistItemType } from '../../@types' + +const initialItems: ChecklistItemType[] = [ + { + code: 'T-01', + itemDesc: + 'As finalidades de tratamento de dados foram definidas na organização?', + recomendations: + 'Crie um documento definindo uma Política de Privacidade e os Termos de Consentimento, descrevendo quais são os objetivos de tratamento de dados e de que forma os dados serão utilizados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-02', + itemDesc: + 'O tratamento de dados pessoais é realizado de acordo com uma base legal?', + recomendations: + 'Na documentação da Política de Privacidade e dos Termos de Consentimento, explique a base legal em que fundamenta o tratamento de dados pessoais. \nExemplos de base legais: I - Mediante o fornecimento de consentimento pelo titular; II - Para o cumprimento de obrigação legal ou regulatória pelo controlador. Ver completo na lei, disponível: Capítulo 2, Seção 1, artigo 7.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-03', + itemDesc: + 'O sistema informa ao titular sobre as finalidades de tratamento de dados pessoais?', + recomendations: + 'O sistema deve disponibilizar a Política de Privacidade e/ou Termos de Consentimento para o titular dos dados, sobre as finalidades de tratamento. ', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-04', + itemDesc: + 'O sistema realiza o tratamento de dados em conformidade com a finalidade apresentada ao titular?', + recomendations: + 'Revisar o consentimento e implementar as alterações que não estão de acordo com o que foi informado ao titular. ', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-05', + itemDesc: + 'O sistema informa para o titular a forma e duração do tratamento dos seus dados pessoais de maneira gratuita e acessível?', + recomendations: + 'Descreva no documento da Política de Privacidade e no Termo de Consentimento, sobre a forma que se realizará o tratamento de dados e qual a sua duração, disponibilizando essas informações sempre para o titular. ', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-06', + itemDesc: + 'O sistema permite o titular consultar sobre a integralidade dos seus dados pessoais de maneira gratuita e acessível?', + recomendations: + 'Crie um canal de comunicação entre o titular e a empresa para facilitar que o titular possa requisitar a integralidade dos seus dados pessoais, sempre de forma gratuita e sem lentidão da operação. Exemplo: Disponibilizar e-mail, telefone, ou criar uma página exclusiva para a comunicação.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-07', + itemDesc: + 'O sistema armazena com exatidão e clareza os dados pessoais coletados dos titulares?', + recomendations: + 'Durante a coleta, garanta que os dados pessoais estão sendo informados corretamente. EX: Usar uma validação de campos nos formulários (CPF, Cep).', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-08', + itemDesc: + 'O sistema mantém atualizados os dados pessoais, conforme necessário e para o cumprimento da finalidade de seu tratamento?', + recomendations: + 'Crie um processo para mapear e revisar os dados pessoais no banco de dados, e se descobrir que os dados pessoais estão incorretos, é necessário corrigir ou apagar o mais rápido possível. E sempre que necessário, pedir ao titular que atualize seus dados para cumprimento da finalidade de tratamento.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-09', + itemDesc: + 'O sistema disponibiliza para o titular as informações sobre a realização do tratamento de dados e a identidade do controlador?', + recomendations: + 'Disponibilizar para o titular na Política de Privacidade e no Termo de Consentimento, informações claras e precisas sobre: a) Finalidades de Processamento; b) A identidade e contato do controlador; c) Os dados envolvidos; d) A base legal; e) Detalhes de transferências de dados fora do brasil; f) Período de retenção de dados; g) Os direitos do titular.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-10', + itemDesc: + 'A organização realiza o tratamento de dados quando baseado no seu legítimo interesse, de forma adequada com a lei?', + recomendations: + 'A empresa realizará uma análise do legítimo interesse para verificar se a hipótese de tratamento pode se enquadrar na base legal. Essa análise deve demonstrar que: a) Há um interesse legítimo válido; b) O processamento de dados é estritamente necessário na busca do interesse legítimo; e c) O processamento não é prejudicial ou anulado pelos direitos do indivíduo.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-11', + itemDesc: + 'O sistema mantém registros das operações de tratamento de dados, especialmente quando baseado em seu legítimo interesse?', + recomendations: + 'Armazenar no banco de dados informações sobre as operações de tratamento dos dados, para possíveis comprovações legais.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-12', + itemDesc: + 'O sistema informa para o titular e autoridades competentes sobre informações de tratamento de dados, principalmente quando baseado em seu legítimo interesse?', + recomendations: + 'Elaborar relatórios das operações de tratamento de dados, principalmente quando baseado no seu legítimo interesse, e disponibilizar quando requisitado para o titular e para as autoridades competentes, como a ANPD.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-13', + itemDesc: + 'O sistema realiza o tratamento de dados pessoais ou dados sensíveis, de forma que não possam ser conectados a um titular de dados, quando for requisitado?', + recomendations: + 'Analisar quais dados pessoais deverão ser anonimizados, ou em outros casos, realizar uma pseudonimização. Exemplo: Uma versão semelhante aos dados originais, mas sem revelar a verdadeira informação do titular.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'T-14', + itemDesc: + 'O sistema informa ao titular de dados sobre a existência de tratamento dos seus dados, antes da coleta?', + recomendations: + 'Disponibilizar ao titular, sempre em destaque e com avisos, sobre os dados pessoais que serão coletados e solicitar o consentimento do titular.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-01', + itemDesc: + 'O sistema permite que o titular forneça o seu consentimento de forma autônoma e clara, para realizar o tratamento de seus dados?', + recomendations: + 'No Termo de Consentimento, recomenda-se usar uma linguagem clara e objetiva, sem propaganda enganosa, evitando caixas pré-marcadas, e também possuir uma opção para que o titular possa negar ou retirar o seu consentimento sem ter nenhum prejuízo.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-02', + itemDesc: + 'O sistema solicita o consentimento específico do titular de dados para comunicar ou compartilhar os dados pessoais com outros controladores?', + recomendations: + 'É obrigatório ser transparente para o titular sobre a finalidade de tratamento dos seus dados. Principalmente quando for compartilhar os dados com outros controladores, o titular deverá ser informado no consentimento de forma clara.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-03', + itemDesc: + 'O sistema armazena o consentimento dado pelo titular para comprovações legais?', + recomendations: + 'Armazenar no banco de dados os registros de consentimento do titular para comprovação.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-04', + itemDesc: + 'O sistema permite ao titular de dados meios para recusar ou retirar o consentimento sem prejuízo ao titular?', + recomendations: + 'O titular deve ter acesso ao seu termo de consentimento ou contrato com a opção de negar a realização do tratamento de dados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-05', + itemDesc: + 'O sistema realiza o tratamento de dados pessoais de crianças e adolescentes somente com consentimento específico pelos pais ou responsável legal, de uma forma acessível e fácil de entender?', + recomendations: + 'Deve certificar que apenas o responsável legítimo poderá dar o consentimento, e somente ele poderá atualizá-lo. Um exemplo é realizar autenticação do titular.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-06', + itemDesc: + 'O sistema informa o titular sobre as mudanças de finalidade e atualização de consentimentos, podendo o titular revogar o consentimento caso discorde das alterações?', + recomendations: + 'Sempre que for alterar a finalidade de tratamento, deverá informar o titular da mudança e solicitar o novo consentimento para realizar o tratamento de dados, com a opção de negar o tratamento caso o titular discorde da alteração.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-07', + itemDesc: + 'O sistema fornece uma declaração de consentimento de forma inteligível e facilmente acessível, não contendo termos abusivos?', + recomendations: + 'O Termo de Consentimento deve ser bem escrito, com linguagem de fácil compreensão, respeitando os direitos do titular.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'C-08', + itemDesc: + 'O sistema informa o titular de dados sobre todos os seus direitos no consentimento?', + recomendations: + 'Informar no Termo de Consentimento e na Política de Privacidade, os direitos que os titulares de dados possuem.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-01', + itemDesc: + 'O sistema armazena os dados pessoais em formato que facilite o titular de dados acessá-los?', + recomendations: + 'Para facilitar o acesso aos titulares, os dados pessoais devem ser armazenados em formato comumente usados por computador, em arquivos estruturados, para que os aplicativos de software possam facilmente identificar, reconhecer e extrair os dados pessoais. alguns exemplos: CSV, XML e JSON.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-02', + itemDesc: + 'O sistema fornece ao titular de dados, meios para registrar reclamações em relação à proteção e ao tratamento de seus dados pessoais?', + recomendations: + 'Disponibilizar o contato de DPO ou responsável pelo tratamento de dados, na política de privacidade ou uma página exclusiva para reclamações.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-03', + itemDesc: + 'O sistema fornece a cópia eletrônica integral dos dados pessoais para o titular?', + recomendations: + 'Transmitir diretamente os dados solicitados ao titular; ou_x000D_ fornecer acesso a uma ferramenta que permite que o próprio titular extraia os dados solicitados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-04', + itemDesc: 'O sistema permite que o titular atualize seus dados pessoais?', + recomendations: + 'De acordo com a finalidade de tratamento, é necessário solicitar a atualização dos dados pessoais do titular. É recomendado criar um mecanismo para que o próprio titular possa atualizar seus dados pessoais.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-05', + itemDesc: + 'O sistema utiliza apenas dados relevantes e adequados à finalidade?', + recomendations: + 'É necessário realizar a minimização dos dados pessoais, utilizando apenas dados relevantes para o tratamento. É recomendado realizar a anonimização de dados desnecessários, realizar bloqueio ou eliminação dos dados pessoais irrelevantes.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-06', + itemDesc: + 'O sistema permite a portabilidade de dados a outro controlador mediante requisição do titular?', + recomendations: + 'Os dados pessoais devem ser armazenados em formato acessível e legível por máquina, para poder exportar os dados do titular quando ele requisitar.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-07', + itemDesc: + 'O sistema permite excluir os dados pessoais do titular, mediante a sua requisição?', + recomendations: + 'Criar um mecanismo para que o titular possa solicitar a remoção dos seus dados pessoais.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-08', + itemDesc: + 'O sistema remove os dados pessoais do titular após o término de seu tratamento?', + recomendations: + 'É necessário excluir os dados pessoais do titular após o cumprimento da finalidade, no prazo estabelecido.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-09', + itemDesc: + 'O sistema fornece informações sobre entidades públicas e privadas, caso tenha realizado compartilhamento de dados pessoais?', + recomendations: + 'Armazenar as informações das entidades e das operações de tratamento realizadas, como o compartilhamento de dados, para comprovações legais e disponibilizar quando requisitado.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-10', + itemDesc: + 'O sistema informa o titular sobre a possibilidade de não fornecer consentimento e sobre as consequências caso seja negada?', + recomendations: + 'Deve fornecer no Termo de Consentimento a possibilidade de recusar o tratamento, e as consequências caso não queira consentir.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-11', + itemDesc: + 'O sistema informa o titular quando o tratamento de dados for uma condição para fornecimento de produto, serviço ou para o exercício de direito?', + recomendations: + 'Deve ficar claro no momento do consentimento do titular os objetivos e pré-requisitos do tratamento de dados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-12', + itemDesc: + 'O sistema permite aos titulares, o direito de se opor a um tratamento de dados de forma fácil?', + recomendations: + 'É necessário fornecer um mecanismo para que o titular consiga retirar seu consentimento. Caso o consentimento seja retirado, os dados do titular não poderão ser mais processados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-13', + itemDesc: + 'O sistema informa ao titular, as razões do não exercício imediato dos direitos do titular?', + recomendations: + 'Quando não for possível realizar imediatamente uma operação requisitada, é necessário enviar uma resposta ao titular em que poderá em alguns casos: I - comunicar que a empresa não é agente de tratamento dos dados e indicar, sempre que possível, o agente responsável; ou II - indicar as razões de fato ou de direito que impedem a adoção imediata da operação.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-14', + itemDesc: + 'O sistema informa ao titular quando uma decisão foi tomada com base no tratamento automatizado, incluídas as decisões destinadas à criação do seu perfil pessoal?', + recomendations: + 'Disponibilizar para o titular as informações das decisões automatizadas que serão realizadas. Caso seja requisitado, fornecer o registro dessas informações.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'D-15', + itemDesc: + 'O sistema fornece a opção do titular contestar ou solicitar revisão da decisão automatizada?', + recomendations: + 'Caso seja requisitado, deverá revisar a decisão automatizada contestada, podendo interromper o tratamento de dados e realiazar um novo consentimento para essa finalidade.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-01', + itemDesc: + 'O sistema realiza o tratamento de dados de uma forma segura, incluindo proteção contra acesso não autorizado?', + recomendations: + 'Garantir a segurança no tratamento dos dados pessoais, possibilitando apenas indivíduos autorizados possam ler, modificar ou excluir dados do sistema. Exemplo recomendado seria criar uma lista de controle de acesso.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-02', + itemDesc: + 'O sistema respeita as normas de transferência de dados pessoais para países internacionais, com grau de proteção de dados pessoais adequado às normas da LGPD?', + recomendations: + 'Verifique se o país destino da transferência tenha um nível de proteção igual ou superior aos previstos na LGPD, e/ou que a empresa destinatária comprove possuir as mesmas garantias de proteção por meio de normas ou certificados. Verifique também se a Autoridade Nacional do Brasil ANPD autoriza tais operações.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-03', + itemDesc: + 'O sistema informa para o titular sobre as informações das transferências dos dados pessoais realizadas para países internacionais?', + recomendations: + 'Informar no documento da Política de Privacidade e no Termo de Consentimento, informações sobre a realização de transferências de dados para países internacionais.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-04', + itemDesc: + 'O sistema utiliza mecanismos para prevenir a ocorrência de danos, destruição ou perda de dados?', + recomendations: + 'É necessário criar mecanismo para prevenir a perda e danos aos dados pessoais do titular. Como boas práticas, deve-se implementar um backup de dados, armazenamento em nuvem, e qualquer outras ações que protegerão as informações originais em caso de danos, destruição ou perda de dados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-05', + itemDesc: + 'O sistema utiliza medidas de proteção adequadas para dados pessoais sensíveis?', + recomendations: + 'É necessário implementar uma camada adicional à segurança e armazenamento, especialmente aos dados pessoais sensíveis. Como exemplos, em alguns casos, usar encriptação dos dados, o controle de acesso, entre outras técnicas recomendadas.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-01', + itemDesc: + 'A organização indica um oficial de proteção de dados (DPO) encarregado pelo tratamento de dados pessoais?', + recomendations: + 'A organização deverá indicar um encarregado pelo tratamento de dados pessoais (DPO), podendo ser pessoa física ou pessoa jurídica, para atuar como canal de comunicação entre o controlador, os titulares dos dados e a Autoridade Nacional (ANPD). A ANPD poderá estabelecer normas complementares sobre as atividades do DPO, inclusive a dispensa de sua indicação, conforme a natureza da organização.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-02', + itemDesc: + 'A organização divulga publicamente de forma clara e objetiva a identidade e as informações de contato do encarregado DPO?', + recomendations: + 'A informação deve estar de forma clara, na Política de Privacidade e no Termo de Consentimento, para que os titulares e a autoridade nacional possam ter acesso facilitado.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-03', + itemDesc: + 'A organização comunica à autoridade nacional e ao titular dos dados a ocorrência de incidente de segurança, que possa acarretar risco ou dano relevante aos titulares, nos prazos estabelecidos pelas autoridades?', + recomendations: + 'Na ocorrência de incidente de segurança, é necessário comunicar ao titular e autoridade nacional ANPD. O prazo recomendado para comunicação pela ANPD é de 2 dias úteis contados da ciência do incidente (mesmo que ainda não esteja confirmado e sob apuração) uma comunicação preliminar deverá ser enviada, sob pena de haver violação à LGPD._x000D_ O conteúdo da comunicação deve abranger, minimamente, o que está previsto no §1º do art. 48, da LGPD, e/ou no formulário disponível em (https://www.gov.br/anpd/pt-br/assuntos/incidente-de-seguranca)', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-04', + itemDesc: + 'A organização realiza avaliação de impacto à proteção de dados pessoais, quando o tratamento resulta em um alto risco para os direitos e liberdades dos titulares dos dados?', + recomendations: + 'A organização deve realizar uma avaliação de impacto de proteção de dados, quando existir um elevado risco no tratamento de dados. Certificar que a avaliação é sempre revisada, especialmente se o grau de risco de tratamento de dados for alto.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-05', + itemDesc: + 'A organização realiza relatório de impactos à proteção de dados pessoais e disponibiliza a autoridade nacional, quando solicitado? ', + recomendations: + 'É necessário realizar um relatório de impactos à proteção de dados e disponibilizar-lo quando requisitado. O relatório deverá conter, no mínimo, a descrição dos tipos de dados coletados, a metodologia utilizada para a coleta e para a garantia da segurança das informações e a análise do controlador com relação a medidas, salvaguardas e mecanismos de mitigação de risco adotados.', + mandatory: true, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-01', + itemDesc: + 'O sistema utiliza boas práticas de proteção de privacidade, como privacy by design?', + recomendations: + 'Como boa prática para o desenvolvimento e adequação do sistema, implementar a metodologia privacy by design.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-02', + itemDesc: + 'O sistema realiza o mapeamento dos dados pessoais e mantém em segurança?', + recomendations: + 'Organizar os locais onde os dados são coletados e armazenados.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-03', + itemDesc: + 'O sistema fornece a confidencialidade, usando medidas técnicas apropriadas?', + recomendations: + 'Realizar a proteção contra acesso não autorizado, no uso de dados pessoais armazenados ou em transição, como exemplos realizar a autenticação e controle de acesso.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-04', + itemDesc: + 'O sistema fornece a integridade dos dados pessoais, impedindo modificações?', + recomendations: + 'Verificar a integridade dos dados durante a coleta e no tratamento dos dados pessoais.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-05', + itemDesc: + 'O sistema mantém registros de auditorias de conformidades e disponibiliza informação ao titular de dados, caso requisitado?', + recomendations: + 'Uma solução seria documentar os dados das auditorias realizadas para fornecer ao titular.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-06', + itemDesc: + 'A organização possui um plano de correção a incidentes de privacidade e segurança?', + recomendations: + 'A organização deve documentar um plano para correção de falhas ou incidentes de segurança.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-07', + itemDesc: + 'O sistema possui certificação ou selos, para comprovar a observância e o cumprimento das normas de proteção de dados pessoais?', + recomendations: + 'É recomendável que os sistemas tenha uma certificação, que comprovem que seguem um código de conduta ou política de segurança confiável para aderência da lei de proteção de dados. Alguns exemplos: ISO 27001, ISO 27701.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'S-08', + itemDesc: + 'O dispositivo possui certificação para comprovar os padrões de qualidade?', + recomendations: + 'Ao utilizar certificações, é comprovado que o dispositivo possui níveis de qualidade e segurança que são determinadas pela regulamentação.\nÉ de grande importância possuir uma certificação, como por exemplo: Certificação Anatel.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-09', + itemDesc: + "O dispositivo é seguro contra ataques de 'força bruta' e/ou outras tentativas abusivas de login?", + recomendations: + 'Um ataque de força bruta é uma maneira de conseguir acesso privilegiado através de inúmeras tentativas. _x000D_\nUma solução é criar mecanismo para bloquear os utilizadores após várias tentativas inválidas de acesso', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-10', + itemDesc: + 'O dispositivo possui mecanismos contra ataques conhecidos atualmente, como buffer overflow, DDoS, entre outros?', + recomendations: + 'Realizar uma bateria de testes de ataques mais conhecidos atualmente como forma de prevenção.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-11', + itemDesc: + 'Os dispositivos utilizam técnicas de proteção para realizar uma comunicação segura?', + recomendations: + 'Ao compartilhar os dados entre os dispositivos, é necessário que técnicas de proteção sejam implementadas.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-12', + itemDesc: 'As senhas ficam encriptadas na base de dados do dispositivo?', + recomendations: + 'As senhas que ficam salvas na base de dados do dispositivo, se elas estiverem encriptografadas, se tornam mais seguras, se alguém tiver acesso ao dispositivo, não conseguirá visualizar a senha facilmente.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-13', + itemDesc: + 'O dispositivo encaminha os dados devidamente para para o seu destino?', + recomendations: + 'Realizar verificações para identificar se os dados estão sendo encaminhados apenas para seu devido destino.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-14', + itemDesc: + 'O dispositivo utiliza protocolos como SSL e TLS para criptografar as comunicações?', + recomendations: + 'Os protocolos (Secure Socket Layer) e TLS (Transport Layer Security) servem para encriptar as comunicações, proporcionando a segurança na troca de dados e das informações.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-15', + itemDesc: + 'A organização cria mecanismos de segurança para os dispositivos com pouco processamento?', + recomendations: + 'Em cenários que dispositivos não suportam protocolos como por exemplo, CoAP. Nesse caso será necessário a interação entre os equipamentos. Uma alternativa é a utilização de gateways, onde enviará as informações para um ponto na rede e realizará o tratamento e procedimentos que o dispositivo restrito não consegue.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-16', + itemDesc: 'O dispositivo recebe atualizações de segurança?', + recomendations: + 'As atualizações de segurança corrigem falhas e o quanto mais rápido essas falhas forem corrigidas, diminui os riscos contra invasões.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-17', + itemDesc: + 'O dispositivo encripta os dados no armazenamento, para evitar a identificação dos dados pessoais?', + recomendations: + 'Ao armazenar os dados pessoais utilizando criptografia, os dados ficarão mais protegidos. Se ocorrer uma violação, os dados não serão facilmente acessados.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-18', + itemDesc: + 'Existe um profissional de segurança fiscalizando atividades suspeitas ou incomuns e pronto para agir imediatamente?', + recomendations: + 'Ter um profissional fiscalizando todo o processo diminuirá bastante as chances de um incidente de dados, visto que ele estará sempre a disposição para agir imediatamente.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-19', + itemDesc: + 'Os dispositivos são alocados em uma VLAN específica, separando-se de ambientes de usuário/visitantes?', + recomendations: + 'Realizar o gerenciamento de toda a rede, separando o acesso a internet de usuários/visitantes com a rede de acesso dos dispositivos.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-20', + itemDesc: + 'Quando a senha do dispositivo é trocada, a organização recebe uma notificação?', + recomendations: + 'É importante a notificação após mudanças de senhas, porque se alguém sem autorização conseguir realizar a mudança de senha, a organização poderá tomar medidas protetivas imediatamente.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'S-21', + itemDesc: + 'A organização recebe notificações em caso de acessos não autorizados ?', + recomendations: + 'Criar mecanismos para notificações se ocorrer acessos indevidos, com o intuito de conseguir agir imediatamente em caso de invasões.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'R-01', + itemDesc: + 'A organização registra as atividades realizadas pelo DPO, para possíveis comprovações legais?', + recomendations: + 'Armazene e documente o histórico de atividades do encarregado DPO.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-02', + itemDesc: + 'A organização realiza capacitação para seus funcionários sobre a aderência à LGPD?', + recomendations: + 'Capacite os funcionários e colaboradores sobre as informações que a LGPD determina. Exemplo: Através de treinamentos, cursos e palestras, certificações e etc.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'general', + }, + { + code: 'R-03', + itemDesc: + 'São realizadas capacitações para os funcionários que utilizam os dispositivos?', + recomendations: + 'Realizar capacitações com os funcionários para garantir a qualidade e eficiência de todo o processo.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'A-01', + itemDesc: + 'A organização fornece credenciais diferentes para cada utilizador?', + recomendations: + 'Deve ser realizado o controle de acesso para cada utilizador, criando uma lista de pessoas autorizadas.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'A-02', + itemDesc: + 'São atribuídas senhas diferentes a cada dispositivo para fortalecer a segurança?', + recomendations: + 'Ao utilizar a mesma senha para cada dispositivo, a segurança fica enfraquecida, o ideal é ter senhas diferentes. Uma solução é utilizar um algoritmo para gerar as senhas', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'A-03', + itemDesc: + 'Houve mudança da senha padrão do dispositivo, para evitar acesso não autorizado?', + recomendations: + 'É de suma importância a mudança imediata da senha padrão do dispositivo, a senha padrão já é disponibilizada no manual dos dispositivos que facilmente é encontrado no site do fabricante.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'A-04', + itemDesc: 'O dispositivo garante que a senha seja forte e segura?', + recomendations: + 'Para o fornecimento de uma maior proteção a segurança, é fundamental senhas fortes, como por exemplo: Uso de letras maiúsculas, letras minúsculas, números e símbolos', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'A-05', + itemDesc: + 'São registradas falhas ou possíveis suspeitas de vulnerabilidade?', + recomendations: + 'Realizar registros de todas as falhas e suspeitas reais, possuir esta documentação pode ajudar na organização e prevenções.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-01', + itemDesc: + 'O dispositivo possui apenas portas em funcionamento, não perminintdo portas desnecessarias? ', + recomendations: + 'Todas as portas que não estão em uso, deverão ser desativadas. Exemplos de portas: rj45 e usb.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-02', + itemDesc: + 'O dispositivo não possibilita o acesso facilmente de pessoas não autorizadas, evitando riscos à segurança física do mesmo?', + recomendations: + 'Analisar o ambiente no qual o dispositivo se encontra, analisando possíveis riscos que o dispositivo poderá sofrer.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-03', + itemDesc: + 'O dispositivo é protegido contra tentativas de ser resetado para configuração de fábrica por pessoas não autorizadas? ', + recomendations: + 'Apenas pessoas autorizadas poderão ter acesso aos dispositivos e deve ter proibição de intrusos, não permitindo utilização do dispositivo. Se o reset de fábrica não estiver restrito, uma solução é a blindagem do equipamento vedando o seu acesso.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-04', + itemDesc: 'O dispositivo é resistente para o ambiente que está alocado?', + recomendations: + 'Realizar uma análise das condições ambientais, como temperatura e umidade, identificando se irá afetar negativamente o dispositivo que irá ser utilizado.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-05', + itemDesc: + 'A manutenção do dispositivo é realizada conforme recomendação do fornecedor?', + recomendations: + 'O ideal é que as manutenções dos dispositivos sejam realizadas nos intervalos recomendados pelo fornecedor e de acordo com suas recomendações.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-06', + itemDesc: + 'A manutenção e consertos dos dispositivos são realizados por uma equipe autorizada?', + recomendations: + 'Para a confiabilidade desse procedimento, é de grande importância ser realizado por uma equipe autorizada.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, + { + code: 'SF-07', + itemDesc: 'São registradas todas as manutenções preventivas?', + recomendations: + 'A manutenção preventiva é a garantia do bom funcionamento do dispositivo e o registro se torna a comprovação e organização de quando realizou-se o procedimento.', + mandatory: false, + answer: undefined, + severityDegree: undefined, + userComment: '', + type: 'IoT', + }, +] + +export { initialItems } + + + + + +import { ItemClassification } from '../../@types' + +const mandatoryItemsClassifications: ItemClassification[] = [ + { + name: 'Sobre transparência de Dados (T)', + tag: 'T-', + }, + { + name: 'Sobre Consentimento do Titular (C)', + tag: 'C-', + }, + { + name: 'Sobre os Direitos do Titular (D)', + tag: 'D-', + }, + { + name: 'Sobre Segurança de Dados (S)', + tag: 'S-', + }, + { + name: 'Sobre Responsabilidade do Controlador (R)', + tag: 'R-', + }, +] + +const nonMandatoryItemsClassifications: ItemClassification[] = [ + { + name: 'Sobre Segurança de Dados (S)', + tag: 'S-', + }, + { + name: 'Sobre Responsabilidade do Controlador (R)', + tag: 'R-', + }, + { + name: 'Acesso ao Dispositivo (A)', + tag: 'A-', + }, + { + name: 'Segurança Física (SF)', + tag: 'SF-', + }, +] + +export { mandatoryItemsClassifications, nonMandatoryItemsClassifications } diff --git a/package.json b/package.json index 3a0cebd..84aaae9 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,11 @@ "pretest:e2e": "run-s test:create-prisma-environment test:install-prisma-environment", "test:e2e": "set NODE_ENV=test&& vitest run --dir src/delivery ", "test:e2e:watch": "set NODE_ENV=test&& vitest --dir src/delivery", + "test:arch": "vitest run --dir test/architecture", + "test:arch:watch": "vitest --dir test/architecture", "test:coverage": "vitest --coverage", "test:ui": "vitest --ui", - "test:all": "run-s test test:e2e", + "test:all": "run-s test test:e2e test:arch", "dev": "ts-node-dev src/main.ts", "start": "node --import=extensionless/register dist/src/main.js", "start:migrate": "npx prisma migrate deploy && npm run start", diff --git a/prisma/migrations/20250220002328_issue_23/migration.sql b/prisma/migrations/20250220002328_issue_23/migration.sql new file mode 100644 index 0000000..8e6c146 --- /dev/null +++ b/prisma/migrations/20250220002328_issue_23/migration.sql @@ -0,0 +1,95 @@ +/* + Warnings: + + - You are about to drop the column `checklist_data` on the `checklists` table. All the data in the column will be lost. + - You are about to drop the column `is_general` on the `checklists` table. All the data in the column will be lost. + - You are about to drop the column `is_iot` on the `checklists` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "checklists" DROP COLUMN "checklist_data", +DROP COLUMN "is_general", +DROP COLUMN "is_iot"; + +-- CreateTable +CREATE TABLE "Laws" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Laws_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Devices" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Devices_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ChecklistItems" ( + "id" SERIAL NOT NULL, + "code" VARCHAR(255) NOT NULL, + "item_desc" VARCHAR(255) NOT NULL, + "recommendations" VARCHAR(255) NOT NULL, + "is_mandatory" BOOLEAN NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ChecklistItems_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ChecklistItemsChecklists" ( + "checklist_id" INTEGER NOT NULL, + "item_id" INTEGER NOT NULL, + "answer" VARCHAR(255) NOT NULL, + "severity_degree" VARCHAR(255), + "user_comment" VARCHAR(255), + + CONSTRAINT "ChecklistItemsChecklists_pkey" PRIMARY KEY ("checklist_id","item_id") +); + +-- CreateTable +CREATE TABLE "_ChecklistItemsToLaws" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "_ChecklistItemsToDevices" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "ChecklistItems_code_key" ON "ChecklistItems"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "_ChecklistItemsToLaws_AB_unique" ON "_ChecklistItemsToLaws"("A", "B"); + +-- CreateIndex +CREATE INDEX "_ChecklistItemsToLaws_B_index" ON "_ChecklistItemsToLaws"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_ChecklistItemsToDevices_AB_unique" ON "_ChecklistItemsToDevices"("A", "B"); + +-- CreateIndex +CREATE INDEX "_ChecklistItemsToDevices_B_index" ON "_ChecklistItemsToDevices"("B"); + +-- AddForeignKey +ALTER TABLE "_ChecklistItemsToLaws" ADD CONSTRAINT "_ChecklistItemsToLaws_A_fkey" FOREIGN KEY ("A") REFERENCES "ChecklistItems"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_ChecklistItemsToLaws" ADD CONSTRAINT "_ChecklistItemsToLaws_B_fkey" FOREIGN KEY ("B") REFERENCES "Laws"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_ChecklistItemsToDevices" ADD CONSTRAINT "_ChecklistItemsToDevices_A_fkey" FOREIGN KEY ("A") REFERENCES "ChecklistItems"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_ChecklistItemsToDevices" ADD CONSTRAINT "_ChecklistItemsToDevices_B_fkey" FOREIGN KEY ("B") REFERENCES "Devices"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250220002934_alter_table_checklist_items_checklists/migration.sql b/prisma/migrations/20250220002934_alter_table_checklist_items_checklists/migration.sql new file mode 100644 index 0000000..ae99520 --- /dev/null +++ b/prisma/migrations/20250220002934_alter_table_checklist_items_checklists/migration.sql @@ -0,0 +1,5 @@ +-- AddForeignKey +ALTER TABLE "ChecklistItemsChecklists" ADD CONSTRAINT "ChecklistItemsChecklists_checklist_id_fkey" FOREIGN KEY ("checklist_id") REFERENCES "checklists"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ChecklistItemsChecklists" ADD CONSTRAINT "ChecklistItemsChecklists_item_id_fkey" FOREIGN KEY ("item_id") REFERENCES "ChecklistItems"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250329154708_rename_tables/migration.sql b/prisma/migrations/20250329154708_rename_tables/migration.sql new file mode 100644 index 0000000..e765b75 --- /dev/null +++ b/prisma/migrations/20250329154708_rename_tables/migration.sql @@ -0,0 +1,135 @@ +/* + Warnings: + + - You are about to drop the `ChecklistItems` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `ChecklistItemsChecklists` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Devices` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Laws` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_ChecklistItemsToDevices` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_ChecklistItemsToLaws` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "ChecklistItemsChecklists" DROP CONSTRAINT "ChecklistItemsChecklists_checklist_id_fkey"; + +-- DropForeignKey +ALTER TABLE "ChecklistItemsChecklists" DROP CONSTRAINT "ChecklistItemsChecklists_item_id_fkey"; + +-- DropForeignKey +ALTER TABLE "_ChecklistItemsToDevices" DROP CONSTRAINT "_ChecklistItemsToDevices_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_ChecklistItemsToDevices" DROP CONSTRAINT "_ChecklistItemsToDevices_B_fkey"; + +-- DropForeignKey +ALTER TABLE "_ChecklistItemsToLaws" DROP CONSTRAINT "_ChecklistItemsToLaws_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_ChecklistItemsToLaws" DROP CONSTRAINT "_ChecklistItemsToLaws_B_fkey"; + +-- DropTable +DROP TABLE "ChecklistItems"; + +-- DropTable +DROP TABLE "ChecklistItemsChecklists"; + +-- DropTable +DROP TABLE "Devices"; + +-- DropTable +DROP TABLE "Laws"; + +-- DropTable +DROP TABLE "_ChecklistItemsToDevices"; + +-- DropTable +DROP TABLE "_ChecklistItemsToLaws"; + +-- CreateTable +CREATE TABLE "laws" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "laws_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "devices" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "devices_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "items" ( + "id" SERIAL NOT NULL, + "code" VARCHAR(255) NOT NULL, + "item_desc" VARCHAR(255) NOT NULL, + "recommendations" VARCHAR(255) NOT NULL, + "is_mandatory" BOOLEAN NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "items_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "checklist_items" ( + "checklist_id" INTEGER NOT NULL, + "item_id" INTEGER NOT NULL, + "answer" VARCHAR(255) NOT NULL, + "severity_degree" VARCHAR(255), + "user_comment" VARCHAR(255), + + CONSTRAINT "checklist_items_pkey" PRIMARY KEY ("checklist_id","item_id") +); + +-- CreateTable +CREATE TABLE "_item_devices" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "_item_laws" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "items_code_key" ON "items"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "_item_devices_AB_unique" ON "_item_devices"("A", "B"); + +-- CreateIndex +CREATE INDEX "_item_devices_B_index" ON "_item_devices"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_item_laws_AB_unique" ON "_item_laws"("A", "B"); + +-- CreateIndex +CREATE INDEX "_item_laws_B_index" ON "_item_laws"("B"); + +-- AddForeignKey +ALTER TABLE "checklist_items" ADD CONSTRAINT "checklist_items_checklist_id_fkey" FOREIGN KEY ("checklist_id") REFERENCES "checklists"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "checklist_items" ADD CONSTRAINT "checklist_items_item_id_fkey" FOREIGN KEY ("item_id") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_item_devices" ADD CONSTRAINT "_item_devices_A_fkey" FOREIGN KEY ("A") REFERENCES "devices"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_item_devices" ADD CONSTRAINT "_item_devices_B_fkey" FOREIGN KEY ("B") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_item_laws" ADD CONSTRAINT "_item_laws_A_fkey" FOREIGN KEY ("A") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_item_laws" ADD CONSTRAINT "_item_laws_B_fkey" FOREIGN KEY ("B") REFERENCES "laws"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250329170257_optional_field_answer/migration.sql b/prisma/migrations/20250329170257_optional_field_answer/migration.sql new file mode 100644 index 0000000..e63d386 --- /dev/null +++ b/prisma/migrations/20250329170257_optional_field_answer/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "checklist_items" ALTER COLUMN "answer" DROP NOT NULL; diff --git a/prisma/migrations/20250502121835_create_relations_checklist_with_law_and_devices/migration.sql b/prisma/migrations/20250502121835_create_relations_checklist_with_law_and_devices/migration.sql new file mode 100644 index 0000000..c1a1238 --- /dev/null +++ b/prisma/migrations/20250502121835_create_relations_checklist_with_law_and_devices/migration.sql @@ -0,0 +1,35 @@ +-- CreateTable +CREATE TABLE "_checklist_laws" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "_checklist_devices" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_checklist_laws_AB_unique" ON "_checklist_laws"("A", "B"); + +-- CreateIndex +CREATE INDEX "_checklist_laws_B_index" ON "_checklist_laws"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_checklist_devices_AB_unique" ON "_checklist_devices"("A", "B"); + +-- CreateIndex +CREATE INDEX "_checklist_devices_B_index" ON "_checklist_devices"("B"); + +-- AddForeignKey +ALTER TABLE "_checklist_laws" ADD CONSTRAINT "_checklist_laws_A_fkey" FOREIGN KEY ("A") REFERENCES "checklists"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_checklist_laws" ADD CONSTRAINT "_checklist_laws_B_fkey" FOREIGN KEY ("B") REFERENCES "laws"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_checklist_devices" ADD CONSTRAINT "_checklist_devices_A_fkey" FOREIGN KEY ("A") REFERENCES "checklists"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_checklist_devices" ADD CONSTRAINT "_checklist_devices_B_fkey" FOREIGN KEY ("B") REFERENCES "devices"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250529155216_create_sections/migration.sql b/prisma/migrations/20250529155216_create_sections/migration.sql new file mode 100644 index 0000000..3ac0bcc --- /dev/null +++ b/prisma/migrations/20250529155216_create_sections/migration.sql @@ -0,0 +1,27 @@ +-- CreateTable +CREATE TABLE "sections" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "sections_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_item_sections" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_item_sections_AB_unique" ON "_item_sections"("A", "B"); + +-- CreateIndex +CREATE INDEX "_item_sections_B_index" ON "_item_sections"("B"); + +-- AddForeignKey +ALTER TABLE "_item_sections" ADD CONSTRAINT "_item_sections_A_fkey" FOREIGN KEY ("A") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_item_sections" ADD CONSTRAINT "_item_sections_B_fkey" FOREIGN KEY ("B") REFERENCES "sections"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250529155950_alter_item_secion_relation/migration.sql b/prisma/migrations/20250529155950_alter_item_secion_relation/migration.sql new file mode 100644 index 0000000..7e7fdda --- /dev/null +++ b/prisma/migrations/20250529155950_alter_item_secion_relation/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + + - You are about to drop the `_item_sections` table. If the table is not empty, all the data it contains will be lost. + - Added the required column `sectionId` to the `items` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "_item_sections" DROP CONSTRAINT "_item_sections_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_item_sections" DROP CONSTRAINT "_item_sections_B_fkey"; + +-- AlterTable +ALTER TABLE "items" ADD COLUMN "sectionId" INTEGER NOT NULL; + +-- DropTable +DROP TABLE "_item_sections"; + +-- AddForeignKey +ALTER TABLE "items" ADD CONSTRAINT "items_sectionId_fkey" FOREIGN KEY ("sectionId") REFERENCES "sections"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20250629183254_alter_table_items_alter_column_section_id/migration.sql b/prisma/migrations/20250629183254_alter_table_items_alter_column_section_id/migration.sql new file mode 100644 index 0000000..76fb3ae --- /dev/null +++ b/prisma/migrations/20250629183254_alter_table_items_alter_column_section_id/migration.sql @@ -0,0 +1,16 @@ +/* + Warnings: + + - You are about to drop the column `sectionId` on the `items` table. All the data in the column will be lost. + - Added the required column `section_id` to the `items` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "items" DROP CONSTRAINT "items_sectionId_fkey"; + +-- AlterTable +ALTER TABLE "items" DROP COLUMN "sectionId", +ADD COLUMN "section_id" INTEGER NOT NULL; + +-- AddForeignKey +ALTER TABLE "items" ADD CONSTRAINT "items_section_id_fkey" FOREIGN KEY ("section_id") REFERENCES "sections"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7cdd0f6..48914c9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -40,16 +40,79 @@ model Systems { } model Checklists { - id Int @id @default(autoincrement()) - userId Int @map("user_id") - systemId Int @map("system_id") - isGeneral Boolean @default(true) @map("is_general") - isIot Boolean @default(false) @map("is_iot") - checklistData Json @map("checklist_data") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @default(now()) @updatedAt @map("updated_at") - user Users @relation(fields: [userId], references: [id], onDelete: Cascade) - system Systems @relation(fields: [systemId], references: [id], onDelete: Cascade) + id Int @id @default(autoincrement()) + userId Int @map("user_id") + systemId Int @map("system_id") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") + user Users @relation(fields: [userId], references: [id], onDelete: Cascade) + system Systems @relation(fields: [systemId], references: [id], onDelete: Cascade) + ItemsChecklists ChecklistItems[] + laws Laws[] @relation("checklist_laws") + devices Devices[] @relation("checklist_devices") @@map("checklists") } + +model Laws { + id Int @id @default(autoincrement()) + name String @db.VarChar(255) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") + items Items[] @relation("item_laws") + checklists Checklists[] @relation("checklist_laws") + + @@map("laws") +} + +model Devices { + id Int @id @default(autoincrement()) + name String @db.VarChar(255) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") + items Items[] @relation("item_devices") + checklists Checklists[] @relation("checklist_devices") + + @@map("devices") +} + +model Items { + id Int @id @default(autoincrement()) + code String @unique @db.VarChar(255) + itemDesc String @map("item_desc") @db.VarChar(255) + recommendations String @db.VarChar(255) + isMandatory Boolean @map("is_mandatory") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") + laws Laws[] @relation("item_laws") + devices Devices[] @relation("item_devices") + ItemsChecklists ChecklistItems[] + sectionId Int @map("section_id") + section Sections @relation(fields: [sectionId], references: [id], onDelete: Cascade) + + @@map("items") +} + +model ChecklistItems { + checklistId Int @map("checklist_id") + checklist Checklists @relation(fields: [checklistId], references: [id], onDelete: Cascade) + itemId Int @map("item_id") + item Items @relation(fields: [itemId], references: [id], onDelete: Cascade) + + answer String? @db.VarChar(255) + severityDegree String? @map("severity_degree") @db.VarChar(255) + userComment String? @map("user_comment") @db.VarChar(255) + + @@id([checklistId, itemId]) + @@map("checklist_items") +} + +model Sections { + id Int @id @default(autoincrement()) + name String @db.VarChar(255) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") + Items Items[] + + @@map("sections") +} diff --git a/prisma/vitest-environment-prisma/prisma-test-environment.ts b/prisma/vitest-environment-prisma/prisma-test-environment.ts index c1bee7d..94ff868 100644 --- a/prisma/vitest-environment-prisma/prisma-test-environment.ts +++ b/prisma/vitest-environment-prisma/prisma-test-environment.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import { randomUUID } from "node:crypto"; import { execSync } from "node:child_process"; -import { Environment } from "vitest"; +import { Environment } from "vitest/environments"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); diff --git a/src/delivery/api/rest/cmd/server.ts b/src/delivery/api/rest/cmd/server.ts index e83cc5c..b0cab78 100644 --- a/src/delivery/api/rest/cmd/server.ts +++ b/src/delivery/api/rest/cmd/server.ts @@ -6,19 +6,20 @@ import bodyParser from "body-parser"; import { PORT } from "../config/config"; import { Router } from "../router/index"; import { env } from "@/domain/env"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; class CmdRest { public app: express.Application; private server: http.Server; - constructor() { + constructor(factory: RepositoryFactory) { this.app = express(); this.middleware(); - this.router(); + this.router(factory); } - private router() { - (() => new Router(this.app))(); + private router(factory: RepositoryFactory) { + (() => new Router(this.app, factory))(); } private middleware() { diff --git a/src/delivery/api/rest/controller/checklist.spec.ts b/src/delivery/api/rest/controller/checklist.spec.ts index 3ccd792..06677ee 100644 --- a/src/delivery/api/rest/controller/checklist.spec.ts +++ b/src/delivery/api/rest/controller/checklist.spec.ts @@ -2,30 +2,42 @@ import { describe, expect, it } from "vitest"; import request from "supertest"; import { restApp } from "../../../../main"; import { createUserAndSystem } from "../../../../../test/utils/createUserAndSystem"; -import { ChecklistEntity } from "../../../../domain/entity/checklist"; -import { MockGenerator } from "../../../../../test/utils/mockGenerator"; - -const checklistDummy: ChecklistEntity = { - id: undefined, - userId: undefined, - checklistData: new MockGenerator().checklistData, - systemId: undefined, - isGeneral: true, - isIot: false, -}; +import { PrismaClient } from "@prisma/client"; +import { createChecklist } from "../../../../../test/utils/createChecklist"; +import { createLaw } from "../../../../../test/utils/createLaw"; +import { createDevice } from "../../../../../test/utils/createDevice"; +import { createItem } from "../../../../../test/utils/createItem"; +import { createSection } from "../../../../../test/utils/createSection"; + +const prisma = new PrismaClient(); describe("Create Checklist (e2e)", () => { it("should create checklist", async () => { const { user, token, system } = await createUserAndSystem(restApp); + // só pode criar uma vez, nos outros testes dessa suite já vai ter criado no banco + const law = await createLaw(prisma); + await createDevice(prisma); + await createSection(prisma); + const item = await createItem(prisma); + const response = await request(restApp) .post("/checklists") .set("Authorization", `Bearer ${token}`) .send({ - ...checklistDummy, userId: user.id, systemId: system.id, isIot: false, + items: [ + { + id: item.id, + answer: "Sim", + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [], }); expect(response.statusCode).toBe(200); @@ -34,20 +46,10 @@ describe("Create Checklist (e2e)", () => { describe("Get Checklist (e2e)", () => { it("should get checklist", async () => { - const { user, token, system } = await createUserAndSystem(restApp); - - const checklistResponse = await request(restApp) - .post("/checklists") - .set("Authorization", `Bearer ${token}`) - .send({ - ...checklistDummy, - userId: user.id, - systemId: system.id, - isIot: false, - }); + const { token, checklist, user, system } = await createChecklist(restApp); const response = await request(restApp) - .get(`/checklists/${checklistResponse.body.checklist.id}`) + .get(`/checklists/${checklist.id}`) .set("Authorization", `Bearer ${token}`); expect(response.statusCode).toBe(200); @@ -58,20 +60,10 @@ describe("Get Checklist (e2e)", () => { describe("Delete Checklist (e2e)", () => { it("should delete checklist", async () => { - const { user, token, system } = await createUserAndSystem(restApp); - - const checklistResponse = await request(restApp) - .post("/checklists") - .set("Authorization", `Bearer ${token}`) - .send({ - ...checklistDummy, - userId: user.id, - systemId: system.id, - isIot: false, - }); + const { token, checklist } = await createChecklist(restApp); const response = await request(restApp) - .delete(`/checklists/${checklistResponse.body.checklist.id}`) + .delete(`/checklists/${checklist.id}`) .set("Authorization", `Bearer ${token}`); expect(response.statusCode).toBe(200); @@ -80,25 +72,23 @@ describe("Delete Checklist (e2e)", () => { describe("Update Checklist (e2e)", () => { it("should update checklist", async () => { - const { user, token, system } = await createUserAndSystem(restApp); - - const checklistResponse = await request(restApp) - .post("/checklists") - .set("Authorization", `Bearer ${token}`) - .send({ - ...checklistDummy, - userId: user.id, - systemId: system.id, - isIot: false, - }); + const { token, system, checklist } = await createChecklist(restApp); const response = await request(restApp) - .put(`/checklists/${checklistResponse.body.checklist.id}`) + .put(`/checklists/${checklist.id}`) .set("Authorization", `Bearer ${token}`) .send({ - ...checklistDummy, systemId: system.id, - isIot: true, + items: [ + { + id: 1, + answer: "Não", + severityDegree: "Grave", + userComment: "Comentário", + }, + ], + laws: [1], + devices: [1], }); expect(response.statusCode).toBe(200); @@ -107,17 +97,7 @@ describe("Update Checklist (e2e)", () => { describe("List User Checklists (e2e)", () => { it("should list user checklists", async () => { - const { user, token, system } = await createUserAndSystem(restApp); - - await request(restApp) - .post("/checklists") - .set("Authorization", `Bearer ${token}`) - .send({ - ...checklistDummy, - userId: user.id, - systemId: system.id, - isIot: false, - }); + const { token, user, system } = await createChecklist(restApp); const response = await request(restApp) .get(`/checklistsByUserId/${user.id}`) @@ -131,17 +111,7 @@ describe("List User Checklists (e2e)", () => { describe("List System Checklists (e2e)", () => { it("should list system checklists", async () => { - const { user, token, system } = await createUserAndSystem(restApp); - - await request(restApp) - .post("/checklists") - .set("Authorization", `Bearer ${token}`) - .send({ - ...checklistDummy, - userId: user.id, - systemId: system.id, - isIot: false, - }); + const { token, user, system } = await createChecklist(restApp); const response = await request(restApp) .get(`/checklistsBySystemId/${system.id}`) diff --git a/src/delivery/api/rest/controller/checklist.ts b/src/delivery/api/rest/controller/checklist.ts index 0a33135..d3a62c9 100644 --- a/src/delivery/api/rest/controller/checklist.ts +++ b/src/delivery/api/rest/controller/checklist.ts @@ -1,38 +1,38 @@ -import * as checklistUseCase from "@/domain/usecase/checklist"; -import * as checklistUcio from "@/domain/usecase/ucio/checklist"; +import * as useCase from "@/domain/usecase/checklist"; import { Request, Response } from "express"; import { InternalServerErrorResponse, SuccessResponse, } from "../response/response"; -import { ChecklistPrismaRepository } from "@/infrastructure/provider/repository/checklist"; -import { SystemPrismaRepository } from "@/infrastructure/provider/repository/system"; -import { UserPrismaRepository } from "@/infrastructure/provider/repository/user"; +import { Controller } from "./controller"; -class CreateChecklistController { +class CreateChecklistController extends Controller { async execute(req: Request, res: Response) { - const { tokenUserId, userId, systemId, checklistData, isGeneral, isIot } = - req.body; + const { tokenUserId, userId, systemId, items, laws, devices } = req.body; - const ucReq = new checklistUcio.CreateChecklistUseCaseRequest( + const ucReq = { tokenUserId, userId, systemId, - checklistData, - isGeneral, - isIot, - ); - - const checklistRepository = new ChecklistPrismaRepository(); - const systemRepository = new SystemPrismaRepository(); - const userRepository = new UserPrismaRepository(); - const usecase = new checklistUseCase.CreateChecklistUseCase( + items, + laws, + devices, + }; + + const checklistRepository = this.factory.makeChecklistRepository(); + const systemRepository = this.factory.makeSystemRepository(); + const userRepository = this.factory.makeUserRepository(); + const itemRepository = this.factory.makeItemRepository(); + const lawRepository = this.factory.makeLawRepository(); + const deviceRepository = this.factory.makeDeviceRepository(); + const ucRes = await new useCase.CreateChecklistUseCase( checklistRepository, systemRepository, userRepository, - ); - - const ucRes = await usecase.execute(ucReq); + itemRepository, + lawRepository, + deviceRepository, + ).execute(ucReq); if (!ucRes.error) { new SuccessResponse().success(res, { checklist: ucRes.checklist }); @@ -42,20 +42,18 @@ class CreateChecklistController { } } -class GetChecklistController { +class GetChecklistController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; const { tokenUserId } = req.body; - const ucReq = new checklistUcio.GetChecklistUseCaseRequest( + const ucReq = { + id: +id, tokenUserId, - +id, - ); + }; - const checklistRepository = new ChecklistPrismaRepository(); - const usecase = new checklistUseCase.GetChecklistUseCase( - checklistRepository, - ); + const checklistRepository = this.factory.makeChecklistRepository(); + const usecase = new useCase.GetChecklistUseCase(checklistRepository); const ucRes = await usecase.execute(ucReq); @@ -67,20 +65,18 @@ class GetChecklistController { } } -class DeleteChecklistController { +class DeleteChecklistController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; const { tokenUserId } = req.body; - const ucReq = new checklistUcio.DeleteChecklistUseCaseRequest( + const ucReq = { + id: +id, tokenUserId, - +id, - ); + }; - const checklistRepository = new ChecklistPrismaRepository(); - const usecase = new checklistUseCase.DeleteChecklistUseCase( - checklistRepository, - ); + const checklistRepository = this.factory.makeChecklistRepository(); + const usecase = new useCase.DeleteChecklistUseCase(checklistRepository); const ucRes = await usecase.execute(ucReq); @@ -92,28 +88,33 @@ class DeleteChecklistController { } } -class UpdateChecklistController { +class UpdateChecklistController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; - const { tokenUserId, systemId, checklistData, isGeneral, isIot } = req.body; + const { tokenUserId, systemId, items, laws, devices } = req.body; - const ucReq = new checklistUcio.UpdateChecklistUseCaseRequest( - +id, - tokenUserId, - systemId, - checklistData, - isGeneral, - isIot, - ); + const checklistRepository = this.factory.makeChecklistRepository(); + const systemRepository = this.factory.makeSystemRepository(); + const itemRepository = this.factory.makeItemRepository(); + const lawRepository = this.factory.makeLawRepository(); + const deviceRepository = this.factory.makeDeviceRepository(); - const checklistRepository = new ChecklistPrismaRepository(); - const systemRepository = new SystemPrismaRepository(); - const usecase = new checklistUseCase.UpdateChecklistUseCase( + const usecase = new useCase.UpdateChecklistUseCase( checklistRepository, systemRepository, + itemRepository, + lawRepository, + deviceRepository, ); - const ucRes = await usecase.execute(ucReq); + const ucRes = await usecase.execute({ + id: +id, + tokenUserId, + systemId, + items, + laws, + devices, + }); if (!ucRes.error) { new SuccessResponse().success(res, undefined); @@ -123,19 +124,19 @@ class UpdateChecklistController { } } -class ListChecklistsByUserIdController { +class ListChecklistsByUserIdController extends Controller { async execute(req: Request, res: Response) { const { userId } = req.params; const { tokenUserId } = req.body; - const ucReq = new checklistUcio.ListChecklistsByUserIdUseCaseRequest( + const ucReq = { tokenUserId, - +userId, - ); + userId: +userId, + }; - const checklistRepository = new ChecklistPrismaRepository(); - const userRepository = new UserPrismaRepository(); - const usecase = new checklistUseCase.ListChecklistsByUserIdUseCase( + const checklistRepository = this.factory.makeChecklistRepository(); + const userRepository = this.factory.makeUserRepository(); + const usecase = new useCase.ListChecklistsByUserIdUseCase( checklistRepository, userRepository, ); @@ -150,19 +151,19 @@ class ListChecklistsByUserIdController { } } -class ListChecklistsBySystemIdController { +class ListChecklistsBySystemIdController extends Controller { async execute(req: Request, res: Response) { const { systemId } = req.params; const { tokenUserId } = req.body; - const ucReq = new checklistUcio.ListChecklistsBySystemIdUseCaseRequest( + const ucReq = { tokenUserId, - +systemId, - ); + systemId: +systemId, + }; - const checklistRepository = new ChecklistPrismaRepository(); - const systemRepository = new SystemPrismaRepository(); - const usecase = new checklistUseCase.ListChecklistsBySystemIdUseCase( + const checklistRepository = this.factory.makeChecklistRepository(); + const systemRepository = this.factory.makeSystemRepository(); + const usecase = new useCase.ListChecklistsBySystemIdUseCase( checklistRepository, systemRepository, ); diff --git a/src/delivery/api/rest/controller/controller.ts b/src/delivery/api/rest/controller/controller.ts new file mode 100644 index 0000000..d76531f --- /dev/null +++ b/src/delivery/api/rest/controller/controller.ts @@ -0,0 +1,16 @@ +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { Request, Response, NextFunction } from "express"; + +export abstract class Controller { + protected factory: RepositoryFactory; + + constructor(factory: RepositoryFactory) { + this.factory = factory; + } + + abstract execute( + req: Request, + res: Response, + next?: NextFunction, + ): Promise; +} diff --git a/src/delivery/api/rest/controller/device.spec.ts b/src/delivery/api/rest/controller/device.spec.ts new file mode 100644 index 0000000..75e8256 --- /dev/null +++ b/src/delivery/api/rest/controller/device.spec.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest"; +import request from "supertest"; +import { restApp } from "../../../../main"; + +describe("List Devices (e2e)", () => { + it("should list devices", async () => { + const response = await request(restApp).get("/devices"); + expect(response.statusCode).toBe(200); + }); +}); diff --git a/src/delivery/api/rest/controller/device.ts b/src/delivery/api/rest/controller/device.ts new file mode 100644 index 0000000..39e8be4 --- /dev/null +++ b/src/delivery/api/rest/controller/device.ts @@ -0,0 +1,21 @@ +import { Request, Response } from "express"; +import * as useCase from "@/domain/usecase/device"; +import { Controller } from "./controller"; +import { + InternalServerErrorResponse, + SuccessResponse, +} from "../response/response"; + +export class ListDevicesController extends Controller { + async execute(req: Request, res: Response) { + const deviceRepository = this.factory.makeDeviceRepository(); + const usecase = new useCase.ListDevicesUseCase(deviceRepository); + const ucRes = await usecase.execute(); + + if (!ucRes.error) { + new SuccessResponse().success(res, { devices: ucRes.devices }); + } else { + new InternalServerErrorResponse().internalServerError(res, ucRes.error); + } + } +} diff --git a/src/delivery/api/rest/controller/item.spec.ts b/src/delivery/api/rest/controller/item.spec.ts new file mode 100644 index 0000000..3d739de --- /dev/null +++ b/src/delivery/api/rest/controller/item.spec.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import request from "supertest"; +import { restApp } from "../../../../main"; +import { createLaw } from "../../../../../test/utils/createLaw"; +import { createDevice } from "../../../../../test/utils/createDevice"; +import { createItem } from "../../../../../test/utils/createItem"; +import { createSection } from "../../../../../test/utils/createSection"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +describe("List Items (e2e)", () => { + it("should list items", async () => { + const law1 = await createLaw(prisma); + const law2 = await createLaw(prisma); + const device = await createDevice(prisma); + const section = await createSection(prisma); + + await createItem(prisma, { + code: "I-01", + itemDesc: "itemDesc", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id, law2.id], + devicesIds: [], + }); + + const response = await request(restApp).get( + `/items?laws=${law1.id},${law2.id}&devices=${device.id}`, + ); + + expect(response.statusCode).toBe(200); + expect(response.body.items.length).toBe(1); + }); +}); diff --git a/src/delivery/api/rest/controller/item.ts b/src/delivery/api/rest/controller/item.ts new file mode 100644 index 0000000..4463915 --- /dev/null +++ b/src/delivery/api/rest/controller/item.ts @@ -0,0 +1,36 @@ +import { Request, Response } from "express"; +import * as useCase from "@/domain/usecase/item"; +import * as ucio from "@/domain/usecase/ucio/item"; +import { Controller } from "./controller"; +import { + InternalServerErrorResponse, + SuccessResponse, +} from "../response/response"; + +export class ListItemsController extends Controller { + async execute(req: Request, res: Response) { + const { laws, devices } = req.query; + + const ucReq: ucio.ListItemsUseCaseRequest = { + laws: typeof laws === "string" ? laws.split(",").map(Number) : [], + devices: + typeof devices === "string" ? devices.split(",").map(Number) : [], + }; + + const itemRepository = this.factory.makeItemRepository(); + const lawRepository = this.factory.makeLawRepository(); + const deviceRepository = this.factory.makeDeviceRepository(); + const usecase = new useCase.ListItemsUseCase( + itemRepository, + lawRepository, + deviceRepository, + ); + const ucRes = await usecase.execute(ucReq); + + if (!ucRes.error) { + new SuccessResponse().success(res, { items: ucRes.items }); + } else { + new InternalServerErrorResponse().internalServerError(res, ucRes.error); + } + } +} diff --git a/src/delivery/api/rest/controller/law.spec.ts b/src/delivery/api/rest/controller/law.spec.ts new file mode 100644 index 0000000..88cf3d2 --- /dev/null +++ b/src/delivery/api/rest/controller/law.spec.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import request from "supertest"; +import { restApp } from "../../../../main"; + +describe("List Laws (e2e)", () => { + it("should list laws", async () => { + const response = await request(restApp).get("/laws"); + + expect(response.statusCode).toBe(200); + }); +}); diff --git a/src/delivery/api/rest/controller/law.ts b/src/delivery/api/rest/controller/law.ts new file mode 100644 index 0000000..f9d018c --- /dev/null +++ b/src/delivery/api/rest/controller/law.ts @@ -0,0 +1,23 @@ +import { Request, Response } from "express"; +import * as useCase from "@/domain/usecase/law"; +import { Controller } from "./controller"; +import { + InternalServerErrorResponse, + SuccessResponse, +} from "../response/response"; + +export class ListLawsController extends Controller { + async execute(req: Request, res: Response) { + const lawRepository = this.factory.makeLawRepository(); + + const usecase = new useCase.ListLawsUseCase(lawRepository); + + const ucRes = await usecase.execute(); + + if (!ucRes.error) { + new SuccessResponse().success(res, { laws: ucRes.laws }); + } else { + new InternalServerErrorResponse().internalServerError(res, ucRes.error); + } + } +} diff --git a/src/delivery/api/rest/controller/system.ts b/src/delivery/api/rest/controller/system.ts index 115edb8..665e332 100644 --- a/src/delivery/api/rest/controller/system.ts +++ b/src/delivery/api/rest/controller/system.ts @@ -1,27 +1,26 @@ -import * as systemUseCase from "@/domain/usecase/system"; -import * as systemUcio from "@/domain/usecase/ucio/system"; +import * as useCase from "@/domain/usecase/system"; +import * as ucio from "@/domain/usecase/ucio/system"; import { Request, Response } from "express"; import { InternalServerErrorResponse, SuccessResponse, } from "../response/response"; -import { SystemPrismaRepository } from "../../../../infrastructure/provider/repository/system"; -import { UserPrismaRepository } from "../../../../infrastructure/provider/repository/user"; +import { Controller } from "./controller"; -class CreateSystemController { +class CreateSystemController extends Controller { async execute(req: Request, res: Response) { const { name, description, userId, tokenUserId } = req.body; - const ucReq = new systemUcio.CreateSystemUseCaseRequest( + const ucReq: ucio.CreateSystemUseCaseRequest = { name, description, userId, tokenUserId, - ); + }; - const systemRepository = new SystemPrismaRepository(); - const userRepository = new UserPrismaRepository(); - const usecase = new systemUseCase.CreateSystemUseCase( + const systemRepository = this.factory.makeSystemRepository(); + const userRepository = this.factory.makeUserRepository(); + const usecase = new useCase.CreateSystemUseCase( systemRepository, userRepository, ); @@ -36,19 +35,19 @@ class CreateSystemController { } } -class ListSystemsByUserIdController { +class ListSystemsByUserIdController extends Controller { async execute(req: Request, res: Response) { const { userId } = req.params; const { tokenUserId } = req.body; - const ucReq = new systemUcio.ListSystemsByUserIdUseCaseRequest( + const ucReq: ucio.ListSystemsByUserIdUseCaseRequest = { tokenUserId, - +userId, - ); + userId: +userId, + }; - const systemRepository = new SystemPrismaRepository(); - const userRepository = new UserPrismaRepository(); - const usecase = new systemUseCase.ListSystemsByUserIdUseCase( + const systemRepository = this.factory.makeSystemRepository(); + const userRepository = this.factory.makeUserRepository(); + const usecase = new useCase.ListSystemsByUserIdUseCase( systemRepository, userRepository, ); @@ -63,14 +62,16 @@ class ListSystemsByUserIdController { } } -class GetSystemController { +class GetSystemController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; - const ucReq = new systemUcio.GetSystemUseCaseRequest(+id); + const ucReq: ucio.GetSystemUseCaseRequest = { + id: +id, + }; - const systemRepository = new SystemPrismaRepository(); - const usecase = new systemUseCase.GetSystemUseCase(systemRepository); + const systemRepository = this.factory.makeSystemRepository(); + const usecase = new useCase.GetSystemUseCase(systemRepository); const ucRes = await usecase.execute(ucReq); @@ -82,15 +83,17 @@ class GetSystemController { } } -class DeleteSystemController { +class DeleteSystemController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; const { tokenUserId } = req.body; - const ucReq = new systemUcio.DeleteSystemUseCaseRequest(+id, tokenUserId); - - const systemRepository = new SystemPrismaRepository(); - const usecase = new systemUseCase.DeleteSystemUseCase(systemRepository); + const ucReq: ucio.DeleteSystemUseCaseRequest = { + id: +id, + tokenUserId, + }; + const systemRepository = this.factory.makeSystemRepository(); + const usecase = new useCase.DeleteSystemUseCase(systemRepository); const ucRes = await usecase.execute(ucReq); @@ -102,20 +105,20 @@ class DeleteSystemController { } } -class UpdateSystemController { +class UpdateSystemController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; const { tokenUserId, name, description } = req.body; - const ucReq = new systemUcio.UpdateSystemUseCaseRequest( - +id, + const ucReq: ucio.UpdateSystemUseCaseRequest = { + id: +id, name, description, tokenUserId, - ); + }; - const systemRepository = new SystemPrismaRepository(); - const usecase = new systemUseCase.UpdateSystemUseCase(systemRepository); + const systemRepository = this.factory.makeSystemRepository(); + const usecase = new useCase.UpdateSystemUseCase(systemRepository); const ucRes = await usecase.execute(ucReq); diff --git a/src/delivery/api/rest/controller/user.ts b/src/delivery/api/rest/controller/user.ts index 46f5950..73f1711 100644 --- a/src/delivery/api/rest/controller/user.ts +++ b/src/delivery/api/rest/controller/user.ts @@ -1,26 +1,26 @@ -import * as userUseCase from "@/domain/usecase/user"; -import * as userUcio from "@/domain/usecase/ucio/user"; +import * as useCase from "@/domain/usecase/user"; +import * as ucio from "@/domain/usecase/ucio/user"; import { InternalServerErrorResponse, SuccessResponse, } from "../response/response"; import { NextFunction, Request, Response } from "express"; -import { UserPrismaRepository } from "../../../../infrastructure/provider/repository/user"; -import { AuthJWTRepository } from "../../../../infrastructure/provider/repository/auth"; +import { Controller } from "./controller"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; -class CreateUserController { +class CreateUserController extends Controller { async execute(req: Request, res: Response) { const { name, office, email, password } = req.body; - const ucReq = new userUcio.CreateUserUseCaseRequest( + const ucReq: ucio.CreateUserUseCaseRequest = { name, office, email, password, - ); + }; - const repository = new UserPrismaRepository(); - const usecase = new userUseCase.CreateUserUseCase(repository); + const repository = this.factory.makeUserRepository(); + const usecase = new useCase.CreateUserUseCase(repository); const ucRes = await usecase.execute(ucReq); @@ -32,18 +32,18 @@ class CreateUserController { } } -class LoginController { +class LoginController extends Controller { async execute(req: Request, res: Response) { const { email, password } = req.body; - const ucReq = new userUcio.LoginUseCaseRequest(email, password); + const ucReq: ucio.LoginUseCaseRequest = { + email, + password, + }; - const userRepository = new UserPrismaRepository(); - const authRepository = new AuthJWTRepository(); - const usecase = new userUseCase.LoginUseCase( - userRepository, - authRepository, - ); + const userRepository = this.factory.makeUserRepository(); + const authRepository = this.factory.makeAuthRepository(); + const usecase = new useCase.LoginUseCase(userRepository, authRepository); const ucRes = await usecase.execute(ucReq); @@ -58,21 +58,24 @@ class LoginController { } } -class VerifyTokenController { +class VerifyTokenController extends Controller { private isMiddleware: boolean; - constructor(isMiddleware: boolean) { + constructor(isMiddleware: boolean, factory: RepositoryFactory) { + super(factory); this.isMiddleware = isMiddleware; } async execute(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization; - const ucReq = new userUcio.VerifyTokenUseCaseRequest(token); + const ucReq: ucio.VerifyTokenUseCaseRequest = { + token, + }; - const userRepository = new UserPrismaRepository(); - const authRepository = new AuthJWTRepository(); - const usecase = new userUseCase.VerifyTokenUseCase( + const userRepository = this.factory.makeUserRepository(); + const authRepository = this.factory.makeAuthRepository(); + const usecase = new useCase.VerifyTokenUseCase( userRepository, authRepository, ); @@ -86,7 +89,7 @@ class VerifyTokenController { req: Request, res: Response, next: NextFunction, - ucRes: userUcio.VerifyTokenUseCaseResponse, + ucRes: ucio.VerifyTokenUseCaseResponse, ) { if (this.isMiddleware) { if (!ucRes.error) { @@ -108,20 +111,27 @@ class VerifyTokenController { } } -class UpdateUserController { +class UpdateUserController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; const { tokenUserId, name, office } = req.body; - const ucReq = new userUcio.UpdateUserUseCaseRequest( + const ucReq: ucio.UpdateUserUseCaseRequest = { + tokenUserId, + id: +id, + name, + office, + }; + /* + const ucReq = new ucio.UpdateUserUseCaseRequest( tokenUserId, +id, name, office, - ); + ); */ - const repository = new UserPrismaRepository(); - const usecase = new userUseCase.UpdateUserUseCase(repository); + const repository = this.factory.makeUserRepository(); + const usecase = new useCase.UpdateUserUseCase(repository); const ucRes = await usecase.execute(ucReq); @@ -133,14 +143,14 @@ class UpdateUserController { } } -class GetUserController { +class GetUserController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; - const ucReq = new userUcio.GetUserUseCaseRequest(+id); + const ucReq: ucio.GetUserUseCaseRequest = { id: +id }; - const repository = new UserPrismaRepository(); - const usecase = new userUseCase.GetUserUseCase(repository); + const repository = this.factory.makeUserRepository(); + const usecase = new useCase.GetUserUseCase(repository); const ucRes = await usecase.execute(ucReq); @@ -152,15 +162,18 @@ class GetUserController { } } -class DeleteUserController { +class DeleteUserController extends Controller { async execute(req: Request, res: Response) { const { id } = req.params; const { tokenUserId } = req.body; - const ucReq = new userUcio.DeleteUserUseCaseRequest(tokenUserId, +id); + const ucReq: ucio.DeleteUserUseCaseRequest = { + tokenUserId, + id: +id, + }; - const repository = new UserPrismaRepository(); - const usecase = new userUseCase.DeleteUserUseCase(repository); + const repository = this.factory.makeUserRepository(); + const usecase = new useCase.DeleteUserUseCase(repository); const ucRes = await usecase.execute(ucReq); diff --git a/src/delivery/api/rest/middleware/verifyToken.ts b/src/delivery/api/rest/middleware/verifyToken.ts index d485414..340b805 100644 --- a/src/delivery/api/rest/middleware/verifyToken.ts +++ b/src/delivery/api/rest/middleware/verifyToken.ts @@ -1,9 +1,12 @@ +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; import { VerifyTokenController } from "../controller/user"; -const verifyTokenMiddlewareBind = new VerifyTokenController(true); +const verifyTokenMiddlewareBind = (factory: RepositoryFactory) => + new VerifyTokenController(true, factory); -const verifyTokenMiddleware = verifyTokenMiddlewareBind.execute.bind( - verifyTokenMiddlewareBind, -); +const verifyTokenMiddleware = (factory: RepositoryFactory) => + verifyTokenMiddlewareBind(factory).execute.bind( + verifyTokenMiddlewareBind(factory), + ); export { verifyTokenMiddleware }; diff --git a/src/delivery/api/rest/router/checklist.ts b/src/delivery/api/rest/router/checklist.ts index 195d3d4..784ff2b 100644 --- a/src/delivery/api/rest/router/checklist.ts +++ b/src/delivery/api/rest/router/checklist.ts @@ -1,4 +1,3 @@ -import { Router } from "express"; import { verifyTokenMiddleware } from "../middleware/verifyToken"; import { CreateChecklistController, @@ -8,48 +7,51 @@ import { ListChecklistsByUserIdController, UpdateChecklistController, } from "../controller/checklist"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { CustomRouter } from "./customRouter"; -class ChecklistRouter { - private router: Router; +class ChecklistRouter extends CustomRouter { + constructor(factory: RepositoryFactory) { + super(); - constructor() { - this.router = Router(); + const create = new CreateChecklistController(factory); + const get = new GetChecklistController(factory); + const listByUser = new ListChecklistsByUserIdController(factory); + const listBySystem = new ListChecklistsBySystemIdController(factory); + const _delete = new DeleteChecklistController(factory); + const update = new UpdateChecklistController(factory); this.router.post( "/checklists", - verifyTokenMiddleware, - new CreateChecklistController().execute, + verifyTokenMiddleware(factory), + create.execute.bind(create), ); this.router.get( "/checklists/:id", - verifyTokenMiddleware, - new GetChecklistController().execute, + verifyTokenMiddleware(factory), + get.execute.bind(get), ); this.router.get( "/checklistsByUserId/:userId", - verifyTokenMiddleware, - new ListChecklistsByUserIdController().execute, + verifyTokenMiddleware(factory), + listByUser.execute.bind(listByUser), ); this.router.get( "/checklistsBySystemId/:systemId", - verifyTokenMiddleware, - new ListChecklistsBySystemIdController().execute, + verifyTokenMiddleware(factory), + listBySystem.execute.bind(listBySystem), ); this.router.delete( "/checklists/:id", - verifyTokenMiddleware, - new DeleteChecklistController().execute, + verifyTokenMiddleware(factory), + _delete.execute.bind(_delete), ); this.router.put( "/checklists/:id", - verifyTokenMiddleware, - new UpdateChecklistController().execute, + verifyTokenMiddleware(factory), + update.execute.bind(update), ); } - - getRouter(): Router { - return this.router; - } } export { ChecklistRouter }; diff --git a/src/delivery/api/rest/router/cors.ts b/src/delivery/api/rest/router/cors.ts index 18ba2bb..5d6b90c 100644 --- a/src/delivery/api/rest/router/cors.ts +++ b/src/delivery/api/rest/router/cors.ts @@ -1,11 +1,10 @@ import cors from "cors"; -import { Request, Response, NextFunction, Router } from "express"; - -class CorsRouter { - private router: Router; +import { Request, Response, NextFunction } from "express"; +import { CustomRouter } from "./customRouter"; +class CorsRouter extends CustomRouter { constructor() { - this.router = Router(); + super(); this.router.options("*", cors()); @@ -19,10 +18,6 @@ class CorsRouter { next(); }); } - - getRouter(): Router { - return this.router; - } } export { CorsRouter }; diff --git a/src/delivery/api/rest/router/customRouter.ts b/src/delivery/api/rest/router/customRouter.ts new file mode 100644 index 0000000..8c2b161 --- /dev/null +++ b/src/delivery/api/rest/router/customRouter.ts @@ -0,0 +1,13 @@ +import { Router } from "express"; + +export abstract class CustomRouter { + protected router: Router; + + constructor() { + this.router = Router(); + } + + getRouter(): Router { + return this.router; + } +} diff --git a/src/delivery/api/rest/router/device.ts b/src/delivery/api/rest/router/device.ts new file mode 100644 index 0000000..c3f1326 --- /dev/null +++ b/src/delivery/api/rest/router/device.ts @@ -0,0 +1,13 @@ +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { ListDevicesController } from "../controller/device"; +import { CustomRouter } from "./customRouter"; + +export class DeviceRouter extends CustomRouter { + constructor(factory: RepositoryFactory) { + super(); + + const list = new ListDevicesController(factory); + + this.router.get("/devices", list.execute.bind(list)); + } +} diff --git a/src/delivery/api/rest/router/index.ts b/src/delivery/api/rest/router/index.ts index 0bddd6f..a098874 100644 --- a/src/delivery/api/rest/router/index.ts +++ b/src/delivery/api/rest/router/index.ts @@ -3,13 +3,20 @@ import { CorsRouter } from "./cors"; import { UserRouter } from "./user"; import { SystemRouter } from "./system"; import { ChecklistRouter } from "./checklist"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { LawRouter } from "./law"; +import { DeviceRouter } from "./device"; +import { ItemRouter } from "./item"; class Router { - constructor(app: express.Router) { + constructor(app: express.Router, factory: RepositoryFactory) { app.use(new CorsRouter().getRouter()); - app.use(new UserRouter().getRouter()); - app.use(new SystemRouter().getRouter()); - app.use(new ChecklistRouter().getRouter()); + app.use(new UserRouter(factory).getRouter()); + app.use(new SystemRouter(factory).getRouter()); + app.use(new ChecklistRouter(factory).getRouter()); + app.use(new LawRouter(factory).getRouter()); + app.use(new DeviceRouter(factory).getRouter()); + app.use(new ItemRouter(factory).getRouter()); } } diff --git a/src/delivery/api/rest/router/item.ts b/src/delivery/api/rest/router/item.ts new file mode 100644 index 0000000..e013b56 --- /dev/null +++ b/src/delivery/api/rest/router/item.ts @@ -0,0 +1,13 @@ +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { ListItemsController } from "../controller/item"; +import { CustomRouter } from "./customRouter"; + +export class ItemRouter extends CustomRouter { + constructor(factory: RepositoryFactory) { + super(); + + const list = new ListItemsController(factory); + + this.router.get("/items", list.execute.bind(list)); + } +} diff --git a/src/delivery/api/rest/router/law.ts b/src/delivery/api/rest/router/law.ts new file mode 100644 index 0000000..49e984f --- /dev/null +++ b/src/delivery/api/rest/router/law.ts @@ -0,0 +1,13 @@ +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { ListLawsController } from "../controller/law"; +import { CustomRouter } from "./customRouter"; + +export class LawRouter extends CustomRouter { + constructor(factory: RepositoryFactory) { + super(); + + const list = new ListLawsController(factory); + + this.router.get("/laws", list.execute.bind(list)); + } +} diff --git a/src/delivery/api/rest/router/system.ts b/src/delivery/api/rest/router/system.ts index 0dd40b8..aa73a9d 100644 --- a/src/delivery/api/rest/router/system.ts +++ b/src/delivery/api/rest/router/system.ts @@ -1,4 +1,3 @@ -import { Router } from "express"; import { verifyTokenMiddleware } from "../middleware/verifyToken"; import { CreateSystemController, @@ -7,39 +6,41 @@ import { ListSystemsByUserIdController, UpdateSystemController, } from "../controller/system"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { CustomRouter } from "./customRouter"; -class SystemRouter { - private router: Router; +class SystemRouter extends CustomRouter { + constructor(factory: RepositoryFactory) { + super(); - constructor() { - this.router = Router(); + const create = new CreateSystemController(factory); + const get = new GetSystemController(factory); + const listByUser = new ListSystemsByUserIdController(factory); + const _delete = new DeleteSystemController(factory); + const update = new UpdateSystemController(factory); this.router.post( "/systems", - verifyTokenMiddleware, - new CreateSystemController().execute, + verifyTokenMiddleware(factory), + create.execute.bind(create), ); - this.router.get("/systems/:id", new GetSystemController().execute); + this.router.get("/systems/:id", get.execute.bind(get)); this.router.get( "/systemsByUserId/:userId", - verifyTokenMiddleware, - new ListSystemsByUserIdController().execute, + verifyTokenMiddleware(factory), + listByUser.execute.bind(listByUser), ); this.router.delete( "/systems/:id", - verifyTokenMiddleware, - new DeleteSystemController().execute, + verifyTokenMiddleware(factory), + _delete.execute.bind(_delete), ); this.router.put( "/systems/:id", - verifyTokenMiddleware, - new UpdateSystemController().execute, + verifyTokenMiddleware(factory), + update.execute.bind(update), ); } - - getRouter(): Router { - return this.router; - } } export { SystemRouter }; diff --git a/src/delivery/api/rest/router/user.ts b/src/delivery/api/rest/router/user.ts index bb63742..a156a94 100644 --- a/src/delivery/api/rest/router/user.ts +++ b/src/delivery/api/rest/router/user.ts @@ -1,4 +1,3 @@ -import { Router } from "express"; import { CreateUserController, DeleteUserController, @@ -8,37 +7,37 @@ import { VerifyTokenController, } from "../controller/user"; import { verifyTokenMiddleware } from "../middleware/verifyToken"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { CustomRouter } from "./customRouter"; -class UserRouter { - private router: Router; +class UserRouter extends CustomRouter { + constructor(factory: RepositoryFactory) { + super(); - constructor() { - this.router = Router(); + const verifyToken = new VerifyTokenController(false, factory); + const login = new LoginController(factory); + const create = new CreateUserController(factory); + const get = new GetUserController(factory); + const _delete = new DeleteUserController(factory); + const update = new UpdateUserController(factory); - const verifyTokenBind = new VerifyTokenController(false); - const verifyToken = verifyTokenBind.execute.bind(verifyTokenBind); + this.router.post("/login", login.execute.bind(login)); - this.router.post("/login", new LoginController().execute); + this.router.get("/token", verifyToken.execute.bind(verifyToken)); - this.router.get("/token", verifyToken); - - this.router.post("/users", new CreateUserController().execute); - this.router.get("/users/:id", new GetUserController().execute); + this.router.post("/users", create.execute.bind(create)); + this.router.get("/users/:id", get.execute.bind(get)); this.router.delete( "/users/:id", - verifyTokenMiddleware, - new DeleteUserController().execute, + verifyTokenMiddleware(factory), + _delete.execute.bind(_delete), ); this.router.put( "/users/:id", - verifyTokenMiddleware, - new UpdateUserController().execute, + verifyTokenMiddleware(factory), + update.execute.bind(update), ); } - - getRouter(): Router { - return this.router; - } } export { UserRouter }; diff --git a/src/domain/@types/index.d.ts b/src/domain/@types/index.d.ts index a88538f..af7ed5c 100644 --- a/src/domain/@types/index.d.ts +++ b/src/domain/@types/index.d.ts @@ -1,11 +1,5 @@ -import { JsonValue } from "@prisma/client/runtime/library"; - -type Json = JsonValue; - -type AuthTokenType = { +export type AuthTokenType = { id: number; iat: number; exp: number; }; - -export type { Json, AuthTokenType }; diff --git a/src/domain/entity/checklist.ts b/src/domain/entity/checklist.ts index b21050c..0ccbcdf 100644 --- a/src/domain/entity/checklist.ts +++ b/src/domain/entity/checklist.ts @@ -1,12 +1,14 @@ -import { Json } from "../@types"; +import { ChecklistItemEntity } from "./checklistItem"; +import { DeviceEntity } from "./device"; +import { LawEntity } from "./law"; class ChecklistEntity { public id: number; public userId: number; public systemId: number; - public checklistData: Json; - public isGeneral?: boolean; - public isIot?: boolean; + public checklistItems?: ChecklistItemEntity[]; + public laws?: LawEntity[]; + public devices?: DeviceEntity[]; public createdAt?: Date; public updatedAt?: Date; @@ -14,18 +16,18 @@ class ChecklistEntity { id: number, userId: number, systemId: number, - checklistData: Json, - isGeneral?: boolean, - isIot?: boolean, + checklistItems?: ChecklistItemEntity[], + laws?: LawEntity[], + devices?: DeviceEntity[], createdAt?: Date, updatedAt?: Date, ) { this.id = id; this.userId = userId; this.systemId = systemId; - this.checklistData = checklistData; - this.isGeneral = isGeneral; - this.isIot = isIot; + this.checklistItems = checklistItems; + this.laws = laws; + this.devices = devices; this.createdAt = createdAt; this.updatedAt = updatedAt; } diff --git a/src/domain/entity/checklistItem.ts b/src/domain/entity/checklistItem.ts new file mode 100644 index 0000000..7543a09 --- /dev/null +++ b/src/domain/entity/checklistItem.ts @@ -0,0 +1,36 @@ +import { ChecklistEntity } from "./checklist"; +import { ItemEntity } from "./item"; + +export type AnswerType = "Sim" | "Não" | "Não se aplica" | undefined; +export const answerTypeArray = ["Sim", "Não", "Não se aplica"] as const; + +export type SeverityDegreeType = "Leve" | "Grave" | "Catastrófico" | undefined; +export const severityDegreeTypeArray = [ + "Leve", + "Grave", + "Catastrófico", +] as const; + +class ChecklistItemEntity { + public checklist: ChecklistEntity; + public item: ItemEntity; + public answer?: AnswerType; + public severityDegree?: SeverityDegreeType; + public userComment?: string; + + constructor( + checklist: ChecklistEntity, + item: ItemEntity, + answer?: AnswerType, + severityDegree?: SeverityDegreeType, + userComment?: string, + ) { + this.checklist = checklist; + this.item = item; + this.answer = answer; + this.severityDegree = severityDegree; + this.userComment = userComment; + } +} + +export { ChecklistItemEntity }; diff --git a/src/domain/entity/device.ts b/src/domain/entity/device.ts new file mode 100644 index 0000000..1ea3683 --- /dev/null +++ b/src/domain/entity/device.ts @@ -0,0 +1,11 @@ +class DeviceEntity { + public id: number; + public name: string; + + constructor(id: number, name: string) { + this.id = id; + this.name = name; + } +} + +export { DeviceEntity }; diff --git a/src/domain/entity/error.ts b/src/domain/entity/error.ts index 6dafdbc..f8777c2 100644 --- a/src/domain/entity/error.ts +++ b/src/domain/entity/error.ts @@ -2,7 +2,7 @@ const INTERNAL_SERVER_ERROR_CODE = 1; const PRE_CONDITIONAL_ERROR_CODE = 2; const TAG_INTERNAL_SERVER_ERROR = "[INTERNAL SERVER ERROR]"; const TAG_PRE_CONDITIONAL_ERROR = "[PRE CONDITIONAL ERROR]"; -const INTERNAL_SERVER_ERROR_MESSAGE = "Erro interno do servidor"; +const INTERNAL_SERVER_ERROR_MESSAGE = "Erro interno do servidor."; const NO_PERMISSION_MESSAGE = "Você não tem permissão para isso."; const DEFAULT_VALIDATION_MESSAGE = "Erro de validação."; diff --git a/src/domain/entity/item.ts b/src/domain/entity/item.ts new file mode 100644 index 0000000..2c377f8 --- /dev/null +++ b/src/domain/entity/item.ts @@ -0,0 +1,37 @@ +import { DeviceEntity } from "./device"; +import { LawEntity } from "./law"; +import { SectionEntity } from "./section"; + +export class ItemEntity { + public id: number; + public code: string; + public itemDesc: string; + public recommendations: string; + public isMandatory: boolean; + public sectionId: number; + public section?: SectionEntity; + public laws?: LawEntity[]; + public devices?: DeviceEntity[]; + + constructor( + id: number, + code: string, + itemDesc: string, + recommendations: string, + isMandatory: boolean, + sectionId: number, + section?: SectionEntity, + laws?: LawEntity[], + devices?: DeviceEntity[], + ) { + this.id = id; + this.code = code; + this.itemDesc = itemDesc; + this.recommendations = recommendations; + this.isMandatory = isMandatory; + this.sectionId = sectionId; + this.section = section; + this.laws = laws; + this.devices = devices; + } +} diff --git a/src/domain/entity/law.ts b/src/domain/entity/law.ts new file mode 100644 index 0000000..5aa7173 --- /dev/null +++ b/src/domain/entity/law.ts @@ -0,0 +1,11 @@ +class LawEntity { + public id: number; + public name: string; + + constructor(id: number, name: string) { + this.id = id; + this.name = name; + } +} + +export { LawEntity }; diff --git a/src/domain/entity/section.ts b/src/domain/entity/section.ts new file mode 100644 index 0000000..361be7c --- /dev/null +++ b/src/domain/entity/section.ts @@ -0,0 +1,9 @@ +export class SectionEntity { + public id: number; + public name: string; + + constructor(id: number, name: string) { + this.id = id; + this.name = name; + } +} diff --git a/src/domain/factory/repositoryFactory.ts b/src/domain/factory/repositoryFactory.ts new file mode 100644 index 0000000..1e04a25 --- /dev/null +++ b/src/domain/factory/repositoryFactory.ts @@ -0,0 +1,19 @@ +import { AuthRepositoryInterface } from "../usecase/repository/auth"; +import { ChecklistRepositoryInterface } from "../usecase/repository/checklist"; +import { DeviceRepositoryInterface } from "../usecase/repository/device"; +import { ItemRepositoryInterface } from "../usecase/repository/item"; +import { LawRepositoryInterface } from "../usecase/repository/law"; +import { SectionRepositoryInterface } from "../usecase/repository/section"; +import { SystemRepositoryInterface } from "../usecase/repository/system"; +import { UserRepositoryInterface } from "../usecase/repository/user"; + +export interface RepositoryFactory { + makeUserRepository(): UserRepositoryInterface; + makeSystemRepository(): SystemRepositoryInterface; + makeChecklistRepository(): ChecklistRepositoryInterface; + makeItemRepository(): ItemRepositoryInterface; + makeLawRepository(): LawRepositoryInterface; + makeDeviceRepository(): DeviceRepositoryInterface; + makeSectionRepository(): SectionRepositoryInterface; + makeAuthRepository(): AuthRepositoryInterface; +} diff --git a/src/domain/usecase/checklist.spec.ts b/src/domain/usecase/checklist.spec.ts index fe623da..c12a864 100644 --- a/src/domain/usecase/checklist.spec.ts +++ b/src/domain/usecase/checklist.spec.ts @@ -1,5 +1,3 @@ -import { UserInMemoryRepository } from "../../../test/repository/user"; -import { SystemInMemoryRepository } from "../../../test/repository/system"; import { CreateChecklistUseCase, DeleteChecklistUseCase, @@ -10,53 +8,88 @@ import { } from "./checklist"; import { MockGenerator } from "../../../test/utils/mockGenerator"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { ChecklistInMemoryRepository } from "../../../test/repository/checklist"; import { expectPreConditionalError } from "../../../test/utils/expectPreConditionalError"; import { ChecklistEntity } from "../entity/checklist"; -import { Json } from "../@types"; import { afterEach } from "node:test"; - -let userRepository: UserInMemoryRepository; -let systemRepository: SystemInMemoryRepository; -let checklistRepository: ChecklistInMemoryRepository; +import { AnswerType, SeverityDegreeType } from "../entity/checklistItem"; +import { testFactory } from "../../../test/factory"; +import { UserRepositoryInterface } from "./repository/user"; +import { SystemRepositoryInterface } from "./repository/system"; +import { ChecklistRepositoryInterface } from "./repository/checklist"; +import { ItemRepositoryInterface } from "./repository/item"; +import { LawRepositoryInterface } from "./repository/law"; +import { DeviceRepositoryInterface } from "./repository/device"; + +let userRepository: UserRepositoryInterface; +let systemRepository: SystemRepositoryInterface; +let checklistRepository: ChecklistRepositoryInterface; +let itemRepository: ItemRepositoryInterface; +let lawRepository: LawRepositoryInterface; +let deviceRepository: DeviceRepositoryInterface; let mockGenerator: MockGenerator; describe("Create Checklist Use Case", () => { let useCase: CreateChecklistUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); - systemRepository = new SystemInMemoryRepository(); - checklistRepository = new ChecklistInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); + systemRepository = testFactory.makeSystemRepository(); + checklistRepository = testFactory.makeChecklistRepository(); + itemRepository = testFactory.makeItemRepository(); + lawRepository = testFactory.makeLawRepository(); + deviceRepository = testFactory.makeDeviceRepository(); useCase = new CreateChecklistUseCase( checklistRepository, systemRepository, userRepository, + itemRepository, + lawRepository, + deviceRepository, + ); + mockGenerator = new MockGenerator( + userRepository, + systemRepository, + null, + itemRepository, + lawRepository, + deviceRepository, ); - mockGenerator = new MockGenerator(userRepository, systemRepository); }); it("should create checklist", async () => { const user = await mockGenerator.createUserMock(); const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user.id, systemId: system.id, tokenUserId: user.id, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], }); expect(result.error).toBe(null); expect(result.checklist.userId).toBe(user.id); expect(result.checklist.systemId).toBe(system.id); - expect(result.checklist.checklistData).toEqual(mockGenerator.checklistData); - expect(result.checklist.isGeneral).toBe(true); - expect(result.checklist.isIot).toBe(false); + expect(result.checklist.checklistItems[0].item.id).toBe(item.id); + expect(result.checklist.laws[0].id).toBe(law.id); + expect(result.checklist.devices[0].id).toBe(device.id); + expect(result.checklist.checklistItems[0].answer).toBe(answer); expect(checklistRepository.items[0]).toEqual(result.checklist); expect(oldSize).toBe(checklistRepository.items.length - 1); }); @@ -64,16 +97,28 @@ describe("Create Checklist Use Case", () => { it("should not create checklist for inexistent user", async () => { const userId = 1; const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId, systemId: system.id, tokenUserId: userId, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error }); @@ -84,36 +129,384 @@ describe("Create Checklist Use Case", () => { it("should not create checklist for inexistent system", async () => { const user = await mockGenerator.createUserMock(); const systemId = 1; + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user.id, systemId, tokenUserId: user.id, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for inexistent item", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const itemId = 1; + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: itemId, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(result.error.message).toContain(itemId); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for two inexistent itens", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const itemIds = [item.id, 2]; + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: itemIds.map((id) => { + return { + id, + answer, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + }), + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error }); expect(result.checklist).toBe(null); + expect(result.error.message).toContain(itemIds[itemIds.length - 1]); expect(oldSize).toBe(checklistRepository.items.length); }); + it("should not create checklist for one existent item and the other inexistent", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const itemIds = [1, 2]; + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: itemIds.map((id) => { + return { + id, + answer, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + }), + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(result.error.message).toContain(itemIds.join(", ")); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist when validation error on item answer", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer: AnswerType = "Não"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist when validation error on item answer part 2", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer: AnswerType = "Não"; + const severityDegree: SeverityDegreeType = "Catastrófico"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should create checklist when answer No in item and item correctly filled", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer: AnswerType = "Não"; + const severityDegree: SeverityDegreeType = "Catastrófico"; + const userComment = "Comentário"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree, + userComment, + }, + ], + laws: [law.id], + devices: [device.id], + }); + + expect(result.error).toBe(null); + expect(result.checklist.userId).toBe(user.id); + expect(result.checklist.systemId).toBe(system.id); + expect(result.checklist.checklistItems[0].item.id).toBe(item.id); + expect(result.checklist.checklistItems[0].answer).toBe(answer); + expect(result.checklist.checklistItems[0].severityDegree).toBe( + severityDegree, + ); + expect(result.checklist.checklistItems[0].userComment).toBe(userComment); + expect(checklistRepository.items[0]).toEqual(result.checklist); + expect(oldSize).toBe(checklistRepository.items.length - 1); + }); + + it("should not create checklist for no item id", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: undefined, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: undefined, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for no item", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [], + laws: [law.id], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for no law", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer: "Sim", + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should be able to create checklist for no device", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer: "Sim", + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.checklist.userId).toBe(user.id); + expect(result.checklist.systemId).toBe(system.id); + expect(result.checklist.devices.length).toBe(0); + expect(oldSize).toBe(checklistRepository.items.length - 1); + }); + it("should not create checklist for user different from authenticated user", async () => { const user = await mockGenerator.createUserMock(); const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user.id, systemId: system.id, tokenUserId: user.id + 1, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error, noPermission: true }); @@ -124,16 +517,28 @@ describe("Create Checklist Use Case", () => { it("should not create checklist for no user", async () => { const user = await mockGenerator.createUserMock(); const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: undefined, systemId: system.id, tokenUserId: user.id, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error }); @@ -144,16 +549,28 @@ describe("Create Checklist Use Case", () => { it("should not create checklist for no system", async () => { const user = await mockGenerator.createUserMock(); await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user.id, systemId: undefined, tokenUserId: user.id, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error }); @@ -165,16 +582,28 @@ describe("Create Checklist Use Case", () => { const user1 = await mockGenerator.createUserMock(); const user2 = await mockGenerator.createUserMock(); const system = await mockGenerator.createSystemMock({ userId: user2.id }); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user1.id, systemId: system.id, tokenUserId: user1.id, - checklistData: mockGenerator.checklistData, - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error, noPermission: true }); @@ -182,43 +611,203 @@ describe("Create Checklist Use Case", () => { expect(oldSize).toBe(checklistRepository.items.length); }); - it("should not create checklist with isGeneral equal to false", async () => { + it("should not create checklist for inexistent law", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const lawId = 1; // inexistent law + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [lawId], + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(result.error.message).toContain(lawId); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for inexistent device", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const deviceId = 1; // inexistent device + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: [deviceId], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(result.error.message).toContain(deviceId); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for two inexistent laws", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const lawIds = [1, 2]; // inexistent laws + const device = await mockGenerator.createDeviceMock(); + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: lawIds, + devices: [device.id], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(result.error.message).toContain(lawIds.join(", ")); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for two inexistent devices", async () => { + const user = await mockGenerator.createUserMock(); + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const deviceIds = [1, 2]; // inexistent devices + + const oldSize = checklistRepository.items.length; + + const answer = "Sim"; + + const result = await useCase.execute({ + userId: user.id, + systemId: system.id, + tokenUserId: user.id, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: deviceIds, + }); + + expectPreConditionalError({ error: result.error }); + expect(result.checklist).toBe(null); + expect(result.error.message).toContain(deviceIds.join(", ")); + expect(oldSize).toBe(checklistRepository.items.length); + }); + + it("should not create checklist for one existent law and the other inexistent", async () => { const user = await mockGenerator.createUserMock(); const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const lawIds = [law.id, 2]; // one existent, one inexistent + const device = await mockGenerator.createDeviceMock(); const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user.id, systemId: system.id, tokenUserId: user.id, - checklistData: mockGenerator.checklistData, - isGeneral: false, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: lawIds, + devices: [device.id], }); expectPreConditionalError({ error: result.error }); expect(result.checklist).toBe(null); + expect(result.error.message).toContain(lawIds[1]); expect(oldSize).toBe(checklistRepository.items.length); }); - it("should not create checklist with checklistData in wrong format", async () => { + it("should not create checklist for one existent device and the other inexistent", async () => { const user = await mockGenerator.createUserMock(); const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + const deviceIds = [device.id, 2]; // one existent, one inexistent const oldSize = checklistRepository.items.length; + const answer = "Sim"; + const result = await useCase.execute({ userId: user.id, systemId: system.id, tokenUserId: user.id, - checklistData: "oi", - isGeneral: true, - isIot: false, + items: [ + { + id: item.id, + answer, + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [law.id], + devices: deviceIds, }); expectPreConditionalError({ error: result.error }); expect(result.checklist).toBe(null); + expect(result.error.message).toContain(deviceIds[1]); expect(oldSize).toBe(checklistRepository.items.length); }); }); @@ -227,7 +816,7 @@ describe("Get Checklist Use Case", () => { let useCase: GetChecklistUseCase; beforeEach(() => { - checklistRepository = new ChecklistInMemoryRepository(); + checklistRepository = testFactory.makeChecklistRepository(); useCase = new GetChecklistUseCase(checklistRepository); mockGenerator = new MockGenerator( undefined, @@ -278,7 +867,7 @@ describe("Delete Checklist Use Case", () => { let useCase: DeleteChecklistUseCase; beforeEach(() => { - checklistRepository = new ChecklistInMemoryRepository(); + checklistRepository = testFactory.makeChecklistRepository(); useCase = new DeleteChecklistUseCase(checklistRepository); mockGenerator = new MockGenerator( undefined, @@ -331,13 +920,25 @@ describe("Update Checklist Use Case", () => { let useCase: UpdateChecklistUseCase; beforeEach(() => { - checklistRepository = new ChecklistInMemoryRepository(); - systemRepository = new SystemInMemoryRepository(); - useCase = new UpdateChecklistUseCase(checklistRepository, systemRepository); + checklistRepository = testFactory.makeChecklistRepository(); + systemRepository = testFactory.makeSystemRepository(); + itemRepository = testFactory.makeItemRepository(); + lawRepository = testFactory.makeLawRepository(); + deviceRepository = testFactory.makeDeviceRepository(); + useCase = new UpdateChecklistUseCase( + checklistRepository, + systemRepository, + itemRepository, + lawRepository, + deviceRepository, + ); mockGenerator = new MockGenerator( undefined, systemRepository, checklistRepository, + itemRepository, + lawRepository, + deviceRepository, ); vi.useFakeTimers(); @@ -349,25 +950,44 @@ describe("Update Checklist Use Case", () => { it("should update checklist", async () => { const system1 = await mockGenerator.createSystemMock(); - - const oldChecklist = { - ...(await mockGenerator.createChecklistMock({ systemId: system1.id })), + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, }; - const system2 = await mockGenerator.createSystemMock(); + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system1.id, + items: [oldItem], + laws: [law.id], + devices: [device.id], + }), + ); - const newChecklistData: Json = { message: "hey" }; - const newIsIot = !oldChecklist.isIot; + const system2 = await mockGenerator.createSystemMock(); const oldSize = checklistRepository.items.length; + const newItem = { + id: item.id, + answer: "Não" as AnswerType, + severityDegree: "Grave" as SeverityDegreeType, + userComment: "Ajustar isso", + }; + const result = await useCase.execute({ id: oldChecklist.id, - checklistData: newChecklistData, systemId: system2.id, tokenUserId: oldChecklist.userId, - isGeneral: oldChecklist.isGeneral, - isIot: newIsIot, + items: [newItem], + laws: [law.id], + devices: [device.id], }); const checklistUpdated = checklistRepository.items.find( @@ -376,38 +996,59 @@ describe("Update Checklist Use Case", () => { expect(result.error).toBe(null); expect(checklistUpdated).toBeInstanceOf(ChecklistEntity); - expect(checklistUpdated.checklistData).toEqual(newChecklistData); - expect(checklistUpdated.checklistData).not.toEqual( - oldChecklist.checklistData, + expect(checklistUpdated.checklistItems[0].answer).toEqual(newItem.answer); + expect(checklistUpdated.checklistItems[0].severityDegree).toEqual( + newItem.severityDegree, + ); + expect(checklistUpdated.checklistItems[0].userComment).toEqual( + newItem.userComment, ); expect(checklistUpdated.systemId).toBe(system2.id); - expect(checklistUpdated.isGeneral).toBe(oldChecklist.isGeneral); - expect(checklistUpdated.isIot).toBe(newIsIot); expect(checklistUpdated.userId).toBe(oldChecklist.userId); expect(oldChecklist).not.toEqual(checklistUpdated); expect(oldSize).toBe(checklistRepository.items.length); expect(checklistUpdated.id).toBe(oldChecklist.id); }); - it("should be able to update only checklistData", async () => { + it("should be able to update only items", async () => { vi.setSystemTime(new Date(2022, 0, 20, 8, 0, 0)); - const system = await mockGenerator.createSystemMock(); - - const oldChecklist = { - ...(await mockGenerator.createChecklistMock({ systemId: system.id })), + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const oldItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, }; - const newChecklistData: Json = { message: "hey" }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [oldItem], + laws: [law.id], + devices: [device.id], + }), + ); const oldSize = checklistRepository.items.length; + const newItem = { + id: item.id, + answer: "Não" as AnswerType, + severityDegree: "Grave" as SeverityDegreeType, + userComment: "Ajustar isso", + }; + const result = await useCase.execute({ id: oldChecklist.id, - checklistData: newChecklistData, systemId: system.id, tokenUserId: oldChecklist.userId, - isGeneral: oldChecklist.isGeneral, - isIot: oldChecklist.isIot, + items: [newItem], + laws: [law.id], + devices: [device.id], }); const checklistUpdated = checklistRepository.items.find( @@ -416,15 +1057,16 @@ describe("Update Checklist Use Case", () => { expect(result.error).toBe(null); expect(checklistUpdated).toBeInstanceOf(ChecklistEntity); - expect(checklistUpdated.checklistData).toEqual(newChecklistData); expect({ ...checklistUpdated, - checklistData: undefined, + checklistItems: undefined, }).toEqual({ ...oldChecklist, - checklistData: undefined, + checklistItems: undefined, }); - expect(oldChecklist).not.toEqual(checklistUpdated); + expect(oldChecklist.checklistItems).not.toEqual( + checklistUpdated.checklistItems, + ); expect(oldSize).toBe(checklistRepository.items.length); expect(checklistUpdated.id).toBe(oldChecklist.id); }); @@ -433,22 +1075,37 @@ describe("Update Checklist Use Case", () => { vi.setSystemTime(new Date(2022, 0, 20, 8, 0, 0)); const system1 = await mockGenerator.createSystemMock(); - - const oldChecklist = { - ...(await mockGenerator.createChecklistMock({ systemId: system1.id })), + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, }; - const system2 = await mockGenerator.createSystemMock(); + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system1.id, + items: [checklistItem], + laws: [law.id], + devices: [device.id], + }), + ); const oldSize = checklistRepository.items.length; + const system2 = await mockGenerator.createSystemMock(); + const result = await useCase.execute({ id: oldChecklist.id, - checklistData: oldChecklist.checklistData, systemId: system2.id, tokenUserId: oldChecklist.userId, - isGeneral: oldChecklist.isGeneral, - isIot: oldChecklist.isIot, + items: [checklistItem], + laws: [law.id], + devices: [device.id], }); const checklistUpdated = checklistRepository.items.find( @@ -458,6 +1115,7 @@ describe("Update Checklist Use Case", () => { expect(result.error).toBe(null); expect(checklistUpdated).toBeInstanceOf(ChecklistEntity); expect(checklistUpdated.systemId).toBe(system2.id); + expect(checklistUpdated.systemId).not.toBe(system1.id); expect({ ...checklistUpdated, systemId: undefined, @@ -470,74 +1128,296 @@ describe("Update Checklist Use Case", () => { expect(checklistUpdated.id).toBe(oldChecklist.id); }); - it("should be able to update only isIot", async () => { - vi.setSystemTime(new Date(2022, 0, 20, 8, 0, 0)); - + it("should remove item not present in updated checklist", async () => { const system = await mockGenerator.createSystemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + const item1 = await mockGenerator.createItemMock(); + const item2 = await mockGenerator.createItemMock(); + + const checklistItem1 = { + id: item1.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; - const oldChecklist = { - ...(await mockGenerator.createChecklistMock({ systemId: system.id })), + const checklistItem2 = { + id: item2.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, }; - const newIsIot = !oldChecklist.isIot; + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem1, checklistItem2], + laws: [law.id], + devices: [device.id], + }), + ); const oldSize = checklistRepository.items.length; + // manda só o item 2 const result = await useCase.execute({ id: oldChecklist.id, - checklistData: oldChecklist.checklistData, systemId: system.id, tokenUserId: oldChecklist.userId, - isGeneral: oldChecklist.isGeneral, - isIot: newIsIot, + items: [checklistItem2], + laws: [law.id], + devices: [device.id], }); const checklistUpdated = checklistRepository.items.find( (item) => item.id === oldChecklist.id, ); + const updatedItemIds = checklistUpdated?.checklistItems.map( + (item) => item.item.id, + ); + expect(result.error).toBe(null); expect(checklistUpdated).toBeInstanceOf(ChecklistEntity); - expect(checklistUpdated.isIot).toBe(newIsIot); - expect({ - ...checklistUpdated, - isIot: undefined, - }).toEqual({ - ...oldChecklist, - isIot: undefined, - }); expect(oldChecklist).not.toEqual(checklistUpdated); + expect(oldChecklist.checklistItems.length).toEqual( + checklistUpdated.checklistItems.length + 1, + ); + + expect(updatedItemIds).toContain(item2.id); // item 2 está presente + expect(updatedItemIds).not.toContain(item1.id); // item 1 foi removido + expect(oldSize).toBe(checklistRepository.items.length); expect(checklistUpdated.id).toBe(oldChecklist.id); }); + it("should add new item to checklist if not previously present", async () => { + const system = await mockGenerator.createSystemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + const item1 = await mockGenerator.createItemMock(); + const item2 = await mockGenerator.createItemMock(); + const item3 = await mockGenerator.createItemMock(); // novo item + + const checklistItem1 = { + id: item1.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const checklistItem2 = { + id: item2.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem1, checklistItem2], + laws: [law.id], + devices: [device.id], + }), + ); + + const checklistItem3 = { + id: item3.id, + answer: "Não" as AnswerType, + severityDegree: "Grave" as SeverityDegreeType, + userComment: "Comentário novo", + }; + + await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [checklistItem1, checklistItem2, checklistItem3], + laws: [law.id], + devices: [device.id], + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedItemIds = checklistUpdated?.checklistItems.map( + (item) => item.item.id, + ); + + expect(updatedItemIds).toContain(item3.id); // novo item foi adicionado + }); + + it("should update fields of existing checklist item", async () => { + const system = await mockGenerator.createSystemMock(); + const item1 = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const originalItem = { + id: item1.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [originalItem], + laws: [law.id], + devices: [device.id], + }), + ); + + const updatedItem = { + id: item1.id, + answer: "Não" as AnswerType, + severityDegree: "Leve" as SeverityDegreeType, + userComment: "Problema identificado", + }; + + await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [updatedItem], + laws: [law.id], + devices: [device.id], + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedItemInstance = checklistUpdated?.checklistItems.find( + (i) => i.item.id === item1.id, + ); + + expect(updatedItemInstance?.answer).toBe(updatedItem.answer); + expect(updatedItemInstance?.severityDegree).toBe( + updatedItem.severityDegree, + ); + expect(updatedItemInstance?.userComment).toBe(updatedItem.userComment); + }); + + it("should remove one item, add another and update one", async () => { + const system = await mockGenerator.createSystemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + const item1 = await mockGenerator.createItemMock(); // será atualizado + const item2 = await mockGenerator.createItemMock(); // será removido + const item3 = await mockGenerator.createItemMock(); // será adicionado + + const checklistItem1 = { + id: item1.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const checklistItem2 = { + id: item2.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem1, checklistItem2], + laws: [law.id], + devices: [device.id], + }), + ); + + const updatedItem1 = { + id: item1.id, + answer: "Não" as AnswerType, + severityDegree: "Catastrófico" as SeverityDegreeType, + userComment: "Atualizado", + }; + + const newItem3 = { + id: item3.id, + answer: "Sim" as AnswerType, + severityDegree: "Leve" as SeverityDegreeType, + userComment: "Novo", + }; + + await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [updatedItem1, newItem3], + laws: [law.id], + devices: [device.id], + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedItemIds = checklistUpdated?.checklistItems.map( + (item) => item.item.id, + ); + + expect(updatedItemIds).toContain(item1.id); // atualizado + expect(updatedItemIds).not.toContain(item2.id); // removido + expect(updatedItemIds).toContain(item3.id); // novo + }); + it("should not update inexistent checklist", async () => { const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; const result = await useCase.execute({ id: 1, - checklistData: mockGenerator.checklistData, systemId: system.id, tokenUserId: 1, - isGeneral: true, - isIot: false, + items: [checklistItem], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error }); }); it("should not update checklist to system that doesnt exist", async () => { + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + const checklist = { ...(await mockGenerator.createChecklistMock()), }; + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + const result = await useCase.execute({ id: checklist.id, - checklistData: mockGenerator.checklistData, systemId: 1, tokenUserId: checklist.userId, - isGeneral: true, - isIot: false, + items: [checklistItem], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error }); @@ -548,14 +1428,24 @@ describe("Update Checklist Use Case", () => { ...(await mockGenerator.createChecklistMock()), }; const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; const result = await useCase.execute({ id: checklist.id, - checklistData: mockGenerator.checklistData, systemId: system.id, tokenUserId: checklist.userId + 1, - isGeneral: true, - isIot: false, + items: [checklistItem], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error, noPermission: true }); @@ -568,27 +1458,213 @@ describe("Update Checklist Use Case", () => { const system = await mockGenerator.createSystemMock({ userId: checklist.userId + 1, }); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + const device = await mockGenerator.createDeviceMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; const result = await useCase.execute({ id: checklist.id, - checklistData: mockGenerator.checklistData, systemId: system.id, tokenUserId: checklist.userId, - isGeneral: true, - isIot: false, + items: [checklistItem], + laws: [law.id], + devices: [device.id], }); expectPreConditionalError({ error: result.error, noPermission: true }); }); + + it("should add new law to checklist if not previously present", async () => { + const system = await mockGenerator.createSystemMock(); + const item = await mockGenerator.createItemMock(); + const device = await mockGenerator.createDeviceMock(); + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); // new law + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem], + laws: [law1.id], + devices: [device.id], + }), + ); + + const result = await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [checklistItem], + laws: [law1.id, law2.id], + devices: [device.id], + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedLawIds = checklistUpdated?.laws.map((law) => law.id); + + expect(result.error).toBe(null); + expect(updatedLawIds).toContain(law1.id); + expect(updatedLawIds).toContain(law2.id); // new law was added + }); + + it("should remove law not present in updated checklist", async () => { + const system = await mockGenerator.createSystemMock(); + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + const item = await mockGenerator.createItemMock(); + const device = await mockGenerator.createDeviceMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem], + laws: [law1.id, law2.id], + devices: [device.id], + }), + ); + + const result = await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [checklistItem], + laws: [law1.id], // remove law2 + devices: [device.id], + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedLawIds = checklistUpdated?.laws.map((law) => law.id); + + expect(result.error).toBe(null); + expect(updatedLawIds).toContain(law1.id); // law1 is present + expect(updatedLawIds).not.toContain(law2.id); // law2 was removed + }); + + it("should add new device to checklist if not previously present", async () => { + const system = await mockGenerator.createSystemMock(); + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); // new device + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem], + laws: [law.id], + devices: [device1.id], + }), + ); + + const result = await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [checklistItem], + laws: [law.id], + devices: [device1.id, device2.id], + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedDeviceIds = checklistUpdated?.devices.map( + (device) => device.id, + ); + + expect(result.error).toBe(null); + expect(updatedDeviceIds).toContain(device1.id); + expect(updatedDeviceIds).toContain(device2.id); // new device was added + }); + + it("should remove device not present in updated checklist", async () => { + const system = await mockGenerator.createSystemMock(); + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); + const item = await mockGenerator.createItemMock(); + const law = await mockGenerator.createLawMock(); + + const checklistItem = { + id: item.id, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }; + + const oldChecklist = structuredClone( + await mockGenerator.createChecklistMock({ + systemId: system.id, + items: [checklistItem], + laws: [law.id], + devices: [device1.id, device2.id], + }), + ); + + const result = await useCase.execute({ + id: oldChecklist.id, + systemId: system.id, + tokenUserId: oldChecklist.userId, + items: [checklistItem], + laws: [law.id], + devices: [device1.id], // remove device2 + }); + + const checklistUpdated = checklistRepository.items.find( + (item) => item.id === oldChecklist.id, + ); + + const updatedDeviceIds = checklistUpdated?.devices.map( + (device) => device.id, + ); + + expect(result.error).toBe(null); + expect(updatedDeviceIds).toContain(device1.id); // device1 is present + expect(updatedDeviceIds).not.toContain(device2.id); // device2 was removed + }); }); describe("List User Checklists Use Case", () => { let useCase: ListChecklistsByUserIdUseCase; beforeEach(() => { - checklistRepository = new ChecklistInMemoryRepository(); - systemRepository = new SystemInMemoryRepository(); - userRepository = new UserInMemoryRepository(); + checklistRepository = testFactory.makeChecklistRepository(); + systemRepository = testFactory.makeSystemRepository(); + userRepository = testFactory.makeUserRepository(); useCase = new ListChecklistsByUserIdUseCase( checklistRepository, userRepository, @@ -639,8 +1715,8 @@ describe("List User Checklists Use Case", () => { expect(result.checklists[1]).toEqual(checklist2); expect(result.checklists.length).toBe(checklistRepository.items.length - 1); - result.checklists.forEach((system) => { - expect(system.userId).toBe(user1.id); + result.checklists.forEach((checklist) => { + expect(checklist.userId).toBe(user1.id); }); }); @@ -683,9 +1759,9 @@ describe("List System Checklists Use Case", () => { let useCase: ListChecklistsBySystemIdUseCase; beforeEach(() => { - checklistRepository = new ChecklistInMemoryRepository(); - systemRepository = new SystemInMemoryRepository(); - userRepository = new UserInMemoryRepository(); + checklistRepository = testFactory.makeChecklistRepository(); + systemRepository = testFactory.makeSystemRepository(); + userRepository = testFactory.makeUserRepository(); useCase = new ListChecklistsBySystemIdUseCase( checklistRepository, systemRepository, @@ -729,12 +1805,15 @@ describe("List System Checklists Use Case", () => { expect(result.checklists[1]).toEqual(checklist2); expect(result.checklists.length).toBe(checklistRepository.items.length - 1); - result.checklists.forEach((system) => { - expect(system.systemId).toBe(system1.id); + result.checklists.forEach((checklist) => { + expect(checklist.systemId).toBe(system1.id); + }); + result.checklists.forEach((checklist) => { + expect(checklist.userId).toBe(user.id); }); }); - it("should list empty list when system doesnt have checklists", async () => { + it("should return empty list when system doesnt have checklists", async () => { const system = await mockGenerator.createSystemMock(); const result = await useCase.execute({ diff --git a/src/domain/usecase/checklist.ts b/src/domain/usecase/checklist.ts index 2b197f5..b86cc58 100644 --- a/src/domain/usecase/checklist.ts +++ b/src/domain/usecase/checklist.ts @@ -1,5 +1,5 @@ -import * as checklistValidateInterface from "../usecase/validate/checklist"; -import * as checklistUcioInterface from "../usecase/ucio/checklist"; +import * as validate from "../usecase/validate/checklist"; +import * as ucio from "../usecase/ucio/checklist"; import { INTERNAL_SERVER_ERROR_MESSAGE, newInternalServerError, @@ -10,69 +10,78 @@ import { import { ChecklistRepositoryInterface } from "./repository/checklist"; import { SystemRepositoryInterface } from "./repository/system"; import { UserRepositoryInterface } from "./repository/user"; +import { ItemRepositoryInterface } from "./repository/item"; +import { ChecklistItemEntity } from "../entity/checklistItem"; +import { ItemEntity } from "../entity/item"; +import { LawRepositoryInterface } from "./repository/law"; +import { DeviceRepositoryInterface } from "./repository/device"; class CreateChecklistUseCase { - public validate: checklistValidateInterface.CreateChecklistUseCaseValidate; + public validate: validate.CreateChecklistUseCaseValidate; public checklistRepository: ChecklistRepositoryInterface; constructor( checklistRepository: ChecklistRepositoryInterface, systemRepository: SystemRepositoryInterface, userRepository: UserRepositoryInterface, + itemRepository: ItemRepositoryInterface, + lawRepository: LawRepositoryInterface, + deviceRepository: DeviceRepositoryInterface, ) { - this.validate = - new checklistValidateInterface.CreateChecklistUseCaseValidate( - systemRepository, - userRepository, - ); + this.validate = new validate.CreateChecklistUseCaseValidate( + systemRepository, + userRepository, + itemRepository, + lawRepository, + deviceRepository, + ); this.checklistRepository = checklistRepository; } async execute( - req: checklistUcioInterface.CreateChecklistUseCaseRequest, - ): Promise { + req: ucio.CreateChecklistUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { - const checklistResp = - await this.checklistRepository.createChecklist(req); + const checklist = await this.checklistRepository.createChecklist(req); - return new checklistUcioInterface.CreateChecklistUseCaseResponse( - checklistResp, - null, - ); + return { + checklist, + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new checklistUcioInterface.CreateChecklistUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + checklist: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new checklistUcioInterface.CreateChecklistUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + checklist: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class GetChecklistUseCase { - public validate: checklistValidateInterface.GetChecklistUseCaseValidate; + public validate: validate.GetChecklistUseCaseValidate; public checklistRepository: ChecklistRepositoryInterface; constructor(checklistRepository: ChecklistRepositoryInterface) { - this.validate = new checklistValidateInterface.GetChecklistUseCaseValidate( + this.validate = new validate.GetChecklistUseCaseValidate( checklistRepository, ); this.checklistRepository = checklistRepository; } async execute( - req: checklistUcioInterface.GetChecklistUseCaseRequest, - ): Promise { + req: ucio.GetChecklistUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -80,125 +89,225 @@ class GetChecklistUseCase { const checklist = await this.checklistRepository.getChecklist(req.id); if (checklist) { - return new checklistUcioInterface.GetChecklistUseCaseResponse( + return { checklist, - null, - ); + error: null, + }; } else { - return new checklistUcioInterface.GetChecklistUseCaseResponse( - null, - newPreConditionalError("Checklist não encontrada"), - ); + return { + checklist: null, + error: newPreConditionalError("Checklist não encontrada"), + }; } } else { - return new checklistUcioInterface.GetChecklistUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + checklist: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new checklistUcioInterface.GetChecklistUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + checklist: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class DeleteChecklistUseCase { - public validate: checklistValidateInterface.DeleteChecklistUseCaseValidate; + public validate: validate.DeleteChecklistUseCaseValidate; public checklistRepository: ChecklistRepositoryInterface; constructor(checklistRepository: ChecklistRepositoryInterface) { - this.validate = - new checklistValidateInterface.DeleteChecklistUseCaseValidate( - checklistRepository, - ); + this.validate = new validate.DeleteChecklistUseCaseValidate( + checklistRepository, + ); this.checklistRepository = checklistRepository; } async execute( - req: checklistUcioInterface.DeleteChecklistUseCaseRequest, - ): Promise { + req: ucio.DeleteChecklistUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { await this.checklistRepository.deleteChecklist(req); - return new checklistUcioInterface.DeleteChecklistUseCaseResponse(null); + return { + error: null, + }; } else { - return new checklistUcioInterface.DeleteChecklistUseCaseResponse( - newPreConditionalError(messageError), - ); + return { + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new checklistUcioInterface.DeleteChecklistUseCaseResponse( - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class UpdateChecklistUseCase { - public validate: checklistValidateInterface.UpdateChecklistUseCaseValidate; + public validate: validate.UpdateChecklistUseCaseValidate; public checklistRepository: ChecklistRepositoryInterface; constructor( checklistRepository: ChecklistRepositoryInterface, systemRepository: SystemRepositoryInterface, + itemRepository: ItemRepositoryInterface, + lawRepository: LawRepositoryInterface, + deviceRepository: DeviceRepositoryInterface, ) { - this.validate = - new checklistValidateInterface.UpdateChecklistUseCaseValidate( - checklistRepository, - systemRepository, - ); + this.validate = new validate.UpdateChecklistUseCaseValidate( + checklistRepository, + systemRepository, + itemRepository, + lawRepository, + deviceRepository, + ); this.checklistRepository = checklistRepository; } async execute( - req: checklistUcioInterface.UpdateChecklistUseCaseRequest, - ): Promise { + req: ucio.UpdateChecklistUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { - await this.checklistRepository.updateChecklist(req); + // Transação + await this.checklistRepository.runInTransaction(async (repo) => { + await this.updateItems(req, repo); - return new checklistUcioInterface.UpdateChecklistUseCaseResponse(null); + await this.updateLaws(req, repo); + await this.updateDevices(req, repo); + + // Atualiza o resto dos campos + await repo.updateChecklist(req); + }); + + return { + error: null, + }; } else { - return new checklistUcioInterface.UpdateChecklistUseCaseResponse( - newPreConditionalError(messageError), - ); + return { + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new checklistUcioInterface.UpdateChecklistUseCaseResponse( - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } + + async updateItems( + req: ucio.UpdateChecklistUseCaseRequest, + repo: ChecklistRepositoryInterface, + ) { + const currentItems = await repo.getItems(req.id); + + const currentIds = currentItems.map((item) => item.item.id); + const newItemsIds = req.items.map((item) => item.id); + + const itemsToDelete = currentIds.filter((id) => !newItemsIds.includes(id)); + const itemsToCreate = req.items.filter( + (item) => !currentIds.includes(item.id), + ); + const itemsToUpdate = req.items.filter((item) => + currentIds.includes(item.id), + ); + + if (itemsToDelete.length) await repo.removeItems(req.id, itemsToDelete); + + if (itemsToCreate.length) + await repo.insertItems( + req.id, + itemsToCreate.map( + (item) => + new ChecklistItemEntity( + null, + new ItemEntity(item.id, null, null, null, null, null, null), + item.answer, + item.severityDegree, + item.userComment, + ), + ), + ); + + for (const item of itemsToUpdate) + await repo.updateItem( + req.id, + new ChecklistItemEntity( + null, + new ItemEntity(item.id, null, null, null, null, null, null), + item.answer, + item.severityDegree, + item.userComment, + ), + ); + } + + async updateLaws( + req: ucio.UpdateChecklistUseCaseRequest, + repo: ChecklistRepositoryInterface, + ) { + const currentItems = await repo.getLaws(req.id); + + const currentIds = currentItems.map((item) => item.id); + const newItemsIds = req.laws; + + const itemsToDelete = currentIds.filter((id) => !newItemsIds.includes(id)); + const itemsToCreate = req.laws.filter((item) => !currentIds.includes(item)); + + if (itemsToDelete.length) await repo.removeLaws(req.id, itemsToDelete); + + if (itemsToCreate.length) await repo.insertLaws(req.id, itemsToCreate); + } + + async updateDevices( + req: ucio.UpdateChecklistUseCaseRequest, + repo: ChecklistRepositoryInterface, + ) { + const currentItems = await repo.getDevices(req.id); + + const currentIds = currentItems.map((item) => item.id); + const newItemsIds = req.devices; + + const itemsToDelete = currentIds.filter((id) => !newItemsIds.includes(id)); + const itemsToCreate = req.devices.filter( + (item) => !currentIds.includes(item), + ); + if (itemsToDelete.length) await repo.removeDevices(req.id, itemsToDelete); + + if (itemsToCreate.length) await repo.insertDevices(req.id, itemsToCreate); + } } class ListChecklistsByUserIdUseCase { - public validate: checklistValidateInterface.ListChecklistsByUserIdUseCaseValidate; + public validate: validate.ListChecklistsByUserIdUseCaseValidate; public checklistRepository: ChecklistRepositoryInterface; constructor( checklistRepository: ChecklistRepositoryInterface, userRepository: UserRepositoryInterface, ) { - this.validate = - new checklistValidateInterface.ListChecklistsByUserIdUseCaseValidate( - userRepository, - ); + this.validate = new validate.ListChecklistsByUserIdUseCaseValidate( + userRepository, + ); this.checklistRepository = checklistRepository; } async execute( - req: checklistUcioInterface.ListChecklistsByUserIdUseCaseRequest, - ): Promise { + req: ucio.ListChecklistsByUserIdUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -206,44 +315,44 @@ class ListChecklistsByUserIdUseCase { const checklists = await this.checklistRepository.listChecklistsByUserId(req); - return new checklistUcioInterface.ListChecklistsByUserIdUseCaseResponse( + return { checklists, - null, - ); + error: null, + }; } else { - return new checklistUcioInterface.ListChecklistsByUserIdUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + checklists: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new checklistUcioInterface.ListChecklistsByUserIdUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + checklists: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class ListChecklistsBySystemIdUseCase { - public validate: checklistValidateInterface.ListChecklistsBySystemIdUseCaseValidate; + public validate: validate.ListChecklistsBySystemIdUseCaseValidate; public checklistRepository: ChecklistRepositoryInterface; constructor( checklistRepository: ChecklistRepositoryInterface, systemRepository: SystemRepositoryInterface, ) { - this.validate = - new checklistValidateInterface.ListChecklistsBySystemIdUseCaseValidate( - systemRepository, - ); + this.validate = new validate.ListChecklistsBySystemIdUseCaseValidate( + systemRepository, + ); this.checklistRepository = checklistRepository; } async execute( - req: checklistUcioInterface.ListChecklistsBySystemIdUseCaseRequest, - ): Promise { + req: ucio.ListChecklistsBySystemIdUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -251,22 +360,23 @@ class ListChecklistsBySystemIdUseCase { const checklists = await this.checklistRepository.listChecklistsBySystemId(req); - return new checklistUcioInterface.ListChecklistsBySystemIdUseCaseResponse( + return { checklists, - null, - ); + error: null, + }; } else { - return new checklistUcioInterface.ListChecklistsBySystemIdUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + checklists: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new checklistUcioInterface.ListChecklistsBySystemIdUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + checklists: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } diff --git a/src/domain/usecase/device.spec.ts b/src/domain/usecase/device.spec.ts new file mode 100644 index 0000000..46571e7 --- /dev/null +++ b/src/domain/usecase/device.spec.ts @@ -0,0 +1,51 @@ +import { beforeEach, describe, expect, it } from "vitest"; +// import { expectPreConditionalError } from "../../../test/utils/expectPreConditionalError"; +import { MockGenerator } from "../../../test/utils/mockGenerator"; +import { testFactory } from "../../../test/factory"; +import { ListDevicesUseCase } from "./device"; +import { DeviceRepositoryInterface } from "./repository/device"; + +let deviceRepository: DeviceRepositoryInterface; +let mockGenerator: MockGenerator; + +describe("List Devices Use Case", () => { + let useCase: ListDevicesUseCase; + + beforeEach(() => { + deviceRepository = testFactory.makeDeviceRepository(); + useCase = new ListDevicesUseCase(deviceRepository); + mockGenerator = new MockGenerator( + null, + null, + null, + null, + null, + deviceRepository, + ); + }); + + it("should list devices", async () => { + const oldSize = deviceRepository.items.length; + + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock({ + name: "Sensor IoT LORA", + }); + + const result = await useCase.execute(); + + expect(result.error).toBe(null); + expect(result.devices.length).toBe(oldSize + 2); + expect(result.devices).toContain(device1); + expect(result.devices).toContain(device2); + }); + + it("should return an empty list when there are no devices", async () => { + const oldSize = deviceRepository.items.length; + + const result = await useCase.execute(); + + expect(result.error).toBe(null); + expect(result.devices.length).toBe(oldSize); + }); +}); diff --git a/src/domain/usecase/device.ts b/src/domain/usecase/device.ts new file mode 100644 index 0000000..20ab2a7 --- /dev/null +++ b/src/domain/usecase/device.ts @@ -0,0 +1,31 @@ +import { DeviceRepositoryInterface } from "./repository/device"; +import * as ucio from "../usecase/ucio/device"; +import { + INTERNAL_SERVER_ERROR_MESSAGE, + newInternalServerError, + TAG_INTERNAL_SERVER_ERROR, +} from "../entity/error"; + +export class ListDevicesUseCase { + private deviceRepository: DeviceRepositoryInterface; + + constructor(deviceRepository: DeviceRepositoryInterface) { + this.deviceRepository = deviceRepository; + } + + async execute(): Promise { + try { + const devices = await this.deviceRepository.list(); + return { + devices, + error: null, + }; + } catch (error) { + console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); + return { + devices: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; + } + } +} diff --git a/src/domain/usecase/item.spec.ts b/src/domain/usecase/item.spec.ts new file mode 100644 index 0000000..2ff8582 --- /dev/null +++ b/src/domain/usecase/item.spec.ts @@ -0,0 +1,402 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { expectPreConditionalError } from "../../../test/utils/expectPreConditionalError"; +import { MockGenerator } from "../../../test/utils/mockGenerator"; +import { testFactory } from "../../../test/factory"; +import { LawRepositoryInterface } from "./repository/law"; +import { DeviceRepositoryInterface } from "./repository/device"; +import { ItemRepositoryInterface } from "./repository/item"; +import { SectionRepositoryInterface } from "./repository/section"; +import { ListItemsUseCase } from "./item"; + +let itemRepository: ItemRepositoryInterface; +let lawRepository: LawRepositoryInterface; +let deviceRepository: DeviceRepositoryInterface; +let sectionRepository: SectionRepositoryInterface; +let mockGenerator: MockGenerator; + +describe("List Items Use Case", () => { + let useCase: ListItemsUseCase; + + beforeEach(() => { + itemRepository = testFactory.makeItemRepository(); + lawRepository = testFactory.makeLawRepository(); + deviceRepository = testFactory.makeDeviceRepository(); + sectionRepository = testFactory.makeSectionRepository(); + useCase = new ListItemsUseCase( + itemRepository, + lawRepository, + deviceRepository, + ); + mockGenerator = new MockGenerator( + null, + null, + null, + itemRepository, + lawRepository, + deviceRepository, + sectionRepository, + ); + }); + + it("should list items", async () => { + const oldSize = itemRepository.items.length; + + const law = await mockGenerator.createLawMock(); + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law.id], + devicesIds: [], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law.id], + devicesIds: [], + }); + + const result = await useCase.execute({ + laws: [law.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize + 2); + expect(result.items).toContain(item1); + expect(result.items).toContain(item2); + }); + + it("should return an empty list when there is no item", async () => { + const oldSize = itemRepository.items.length; + const law = await mockGenerator.createLawMock(); + + const result = await useCase.execute({ + laws: [law.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize); + }); + + it("should not list items without requested laws", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id, law2.id], + devicesIds: [], + }); + + const result = await useCase.execute({ + laws: [law2.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize + 1); + expect(result.items).not.toContain(item1); + expect(result.items).toContain(item2); + }); + + it("should list all items with at least one requested law", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law2.id], + devicesIds: [], + }); + + const result = await useCase.execute({ + laws: [law1.id, law2.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize + 2); + expect(result.items).toContain(item1); + expect(result.items).toContain(item2); + }); + + it("should not list items when requested laws array is empty", async () => { + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + const section = await mockGenerator.createSectionMock(); + + await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + lawsIds: [law1.id], + sectionId: section.id, + devicesIds: [], + }); + await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + lawsIds: [law2.id], + sectionId: section.id, + devicesIds: [], + }); + + const result = await useCase.execute({ + laws: [], + devices: [], + }); + + expectPreConditionalError({ error: result.error }); + expect(result.items).toBe(null); + }); + + it("should not list items without requested devices", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); + + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [device1.id], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law2.id], + devicesIds: [device1.id, device2.id], + }); + + const result = await useCase.execute({ + laws: [law1.id, law2.id], + devices: [device2.id], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize + 1); + expect(result.items).not.toContain(item1); + expect(result.items).toContain(item2); + }); + + it("should list all items with at least one requested device", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); + + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [device1.id], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law2.id], + devicesIds: [device2.id], + }); + + const result = await useCase.execute({ + laws: [law1.id, law2.id], + devices: [device1.id, device2.id], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize + 2); + expect(result.items).toContain(item1); + expect(result.items).toContain(item2); + }); + + it("should list empty items when there is a requested law that any item contains", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + const law3 = await mockGenerator.createLawMock(); + + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); + + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [device1.id], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law2.id], + devicesIds: [device2.id], + }); + + const result = await useCase.execute({ + laws: [law3.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize); + expect(result.items).not.toContain(item1); + expect(result.items).not.toContain(item2); + }); + + it("should list empty items when there items have devices but request asks for no device", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); + + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [device1.id], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law2.id], + devicesIds: [device2.id], + }); + + const result = await useCase.execute({ + laws: [law1.id, law2.id], + devices: [], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize); + expect(result.items).not.toContain(item1); + expect(result.items).not.toContain(item2); + }); + + it("should list empty items when there items have devices but request asks for no device", async () => { + const oldSize = itemRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock(); + + const device1 = await mockGenerator.createDeviceMock(); + const device2 = await mockGenerator.createDeviceMock(); + + const section = await mockGenerator.createSectionMock(); + + const item1 = await mockGenerator.createItemMock({ + code: "I-01", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law1.id], + devicesIds: [], + }); + const item2 = await mockGenerator.createItemMock({ + code: "I-02", + itemDesc: "descricao", + recommendations: "recommendations", + isMandatory: true, + sectionId: section.id, + lawsIds: [law2.id], + devicesIds: [device2.id], + }); + + const result = await useCase.execute({ + laws: [law1.id, law2.id], + devices: [device1.id, device2.id], + }); + + expect(result.error).toBe(null); + expect(result.items.length).toBe(oldSize + 2); + expect(result.items).toContain(item1); + expect(result.items).toContain(item2); + }); +}); diff --git a/src/domain/usecase/item.ts b/src/domain/usecase/item.ts new file mode 100644 index 0000000..b3cf3ef --- /dev/null +++ b/src/domain/usecase/item.ts @@ -0,0 +1,57 @@ +import { ItemRepositoryInterface } from "./repository/item"; +import * as validate from "../usecase/validate/item"; +import * as ucio from "../usecase/ucio/item"; +import { + INTERNAL_SERVER_ERROR_MESSAGE, + newInternalServerError, + newPreConditionalError, + TAG_INTERNAL_SERVER_ERROR, + TAG_PRE_CONDITIONAL_ERROR, +} from "../entity/error"; +import { LawRepositoryInterface } from "./repository/law"; +import { DeviceRepositoryInterface } from "./repository/device"; + +export class ListItemsUseCase { + public validate: validate.ListItemsUseCaseValidate; + private itemRepository: ItemRepositoryInterface; + + constructor( + itemRepository: ItemRepositoryInterface, + lawRepository: LawRepositoryInterface, + deviceRepository: DeviceRepositoryInterface, + ) { + this.itemRepository = itemRepository; + this.validate = new validate.ListItemsUseCaseValidate( + lawRepository, + deviceRepository, + ); + } + + async execute( + req: ucio.ListItemsUseCaseRequest, + ): Promise { + try { + const messageError = await this.validate.validate(req); + + if (!messageError) { + const items = await this.itemRepository.list(req); + return { + items, + error: null, + }; + } else { + console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); + return { + items: null, + error: newPreConditionalError(messageError), + }; + } + } catch (error) { + console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); + return { + items: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; + } + } +} diff --git a/src/domain/usecase/law.spec.ts b/src/domain/usecase/law.spec.ts new file mode 100644 index 0000000..9498fa5 --- /dev/null +++ b/src/domain/usecase/law.spec.ts @@ -0,0 +1,44 @@ +import { beforeEach, describe, expect, it } from "vitest"; +// import { expectPreConditionalError } from "../../../test/utils/expectPreConditionalError"; +import { MockGenerator } from "../../../test/utils/mockGenerator"; +import { testFactory } from "../../../test/factory"; +import { ListLawsUseCase } from "./law"; +import { LawRepositoryInterface } from "./repository/law"; + +let lawRepository: LawRepositoryInterface; +let mockGenerator: MockGenerator; + +describe("List Laws Use Case", () => { + let useCase: ListLawsUseCase; + + beforeEach(() => { + lawRepository = testFactory.makeLawRepository(); + useCase = new ListLawsUseCase(lawRepository); + mockGenerator = new MockGenerator(null, null, null, null, lawRepository); + }); + + it("should list laws", async () => { + const oldSize = lawRepository.items.length; + + const law1 = await mockGenerator.createLawMock(); + const law2 = await mockGenerator.createLawMock({ + name: "GDPR", + }); + + const result = await useCase.execute(); + + expect(result.error).toBe(null); + expect(result.laws.length).toBe(oldSize + 2); + expect(result.laws).toContain(law1); + expect(result.laws).toContain(law2); + }); + + it("should return an empty list when there is no laws", async () => { + const oldSize = lawRepository.items.length; + + const result = await useCase.execute(); + + expect(result.error).toBe(null); + expect(result.laws.length).toBe(oldSize); + }); +}); diff --git a/src/domain/usecase/law.ts b/src/domain/usecase/law.ts new file mode 100644 index 0000000..340d39f --- /dev/null +++ b/src/domain/usecase/law.ts @@ -0,0 +1,31 @@ +import { LawRepositoryInterface } from "./repository/law"; +import * as ucio from "../usecase/ucio/law"; +import { + INTERNAL_SERVER_ERROR_MESSAGE, + newInternalServerError, + TAG_INTERNAL_SERVER_ERROR, +} from "../entity/error"; + +export class ListLawsUseCase { + private lawRepository: LawRepositoryInterface; + + constructor(lawRepository: LawRepositoryInterface) { + this.lawRepository = lawRepository; + } + + async execute(): Promise { + try { + const laws = await this.lawRepository.list(); + return { + laws, + error: null, + }; + } catch (error) { + console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); + return { + laws: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; + } + } +} diff --git a/src/domain/usecase/repository/checklist.ts b/src/domain/usecase/repository/checklist.ts index c1a3496..08c0c15 100644 --- a/src/domain/usecase/repository/checklist.ts +++ b/src/domain/usecase/repository/checklist.ts @@ -1,4 +1,7 @@ import { ChecklistEntity } from "../../entity/checklist"; +import { ChecklistItemEntity } from "../../entity/checklistItem"; +import { DeviceEntity } from "../../entity/device"; +import { LawEntity } from "../../entity/law"; import { CreateChecklistUseCaseRequest, DeleteChecklistUseCaseRequest, @@ -8,6 +11,9 @@ import { } from "../ucio/checklist"; interface ChecklistRepositoryInterface { + // apenas para testes + items?: ChecklistEntity[]; + createChecklist(req: CreateChecklistUseCaseRequest): Promise; getChecklist(id: number): Promise; deleteChecklist(req: DeleteChecklistUseCaseRequest): Promise; @@ -18,6 +24,24 @@ interface ChecklistRepositoryInterface { listChecklistsBySystemId( req: ListChecklistsBySystemIdUseCaseRequest, ): Promise; + + // items + getItems(id: number): Promise; + insertItems(id: number, items: ChecklistItemEntity[]): Promise; + removeItems(id: number, itemsIds: number[]): Promise; + updateItem(id: number, item: ChecklistItemEntity): Promise; + + // laws + getLaws(id: number): Promise; + insertLaws(id: number, lawsIds: number[]): Promise; + removeLaws(id: number, lawsIds: number[]): Promise; + + // devices + getDevices(id: number): Promise; + insertDevices(id: number, devicesIds: number[]): Promise; + removeDevices(id: number, devicesIds: number[]): Promise; + + runInTransaction(fn: (repo: this) => Promise): Promise; } export { ChecklistRepositoryInterface }; diff --git a/src/domain/usecase/repository/device.ts b/src/domain/usecase/repository/device.ts new file mode 100644 index 0000000..9024fcc --- /dev/null +++ b/src/domain/usecase/repository/device.ts @@ -0,0 +1,10 @@ +import { DeviceEntity } from "../../entity/device"; +import { CreateDeviceUseCaseRequest } from "../ucio/device"; + +export interface DeviceRepositoryInterface { + items?: DeviceEntity[]; + + create(req: CreateDeviceUseCaseRequest): Promise; + existByIds(ids: number[]): Promise; + list(): Promise; +} diff --git a/src/domain/usecase/repository/item.ts b/src/domain/usecase/repository/item.ts new file mode 100644 index 0000000..75d812b --- /dev/null +++ b/src/domain/usecase/repository/item.ts @@ -0,0 +1,15 @@ +import { ItemEntity } from "../../entity/item"; +import { + CreateItemUseCaseRequest, + ListItemsUseCaseRequest, +} from "../ucio/item"; + +interface ItemRepositoryInterface { + items?: ItemEntity[]; + + createItem(req: CreateItemUseCaseRequest): Promise; + itemsExistByIds(ids: number[]): Promise; + list(req: ListItemsUseCaseRequest): Promise; +} + +export { ItemRepositoryInterface }; diff --git a/src/domain/usecase/repository/law.ts b/src/domain/usecase/repository/law.ts new file mode 100644 index 0000000..57bf21d --- /dev/null +++ b/src/domain/usecase/repository/law.ts @@ -0,0 +1,10 @@ +import { LawEntity } from "../../entity/law"; +import { CreateLawUseCaseRequest } from "../ucio/law"; + +export interface LawRepositoryInterface { + items?: LawEntity[]; + + create(req: CreateLawUseCaseRequest): Promise; + existByIds(ids: number[]): Promise; + list(): Promise; +} diff --git a/src/domain/usecase/repository/section.ts b/src/domain/usecase/repository/section.ts new file mode 100644 index 0000000..09ef555 --- /dev/null +++ b/src/domain/usecase/repository/section.ts @@ -0,0 +1,6 @@ +import { SectionEntity } from "../../entity/section"; +import { CreateSectionUseCaseRequest } from "../ucio/section"; + +export interface SectionRepositoryInterface { + create(req: CreateSectionUseCaseRequest): Promise; +} diff --git a/src/domain/usecase/repository/system.ts b/src/domain/usecase/repository/system.ts index 56ec4e7..8b6e4d5 100644 --- a/src/domain/usecase/repository/system.ts +++ b/src/domain/usecase/repository/system.ts @@ -7,6 +7,8 @@ import { } from "../ucio/system"; interface SystemRepositoryInterface { + items?: SystemEntity[]; + createSystem(req: CreateSystemUseCaseRequest): Promise; listSystemsByUserId( req: ListSystemsByUserIdUseCaseRequest, diff --git a/src/domain/usecase/repository/user.ts b/src/domain/usecase/repository/user.ts index d9dd875..8519bf9 100644 --- a/src/domain/usecase/repository/user.ts +++ b/src/domain/usecase/repository/user.ts @@ -7,6 +7,8 @@ import { } from "../ucio/user"; interface UserRepositoryInterface { + items?: UserEntity[]; + checkUserByEmailExists(email: string, id?: number): Promise; createUser(req: CreateUserUseCaseRequest): Promise; login(req: LoginUseCaseRequest): Promise; diff --git a/src/domain/usecase/system.spec.ts b/src/domain/usecase/system.spec.ts index 1773fa9..56bdbf7 100644 --- a/src/domain/usecase/system.spec.ts +++ b/src/domain/usecase/system.spec.ts @@ -1,6 +1,4 @@ import { beforeEach, describe, expect, it } from "vitest"; -import { UserInMemoryRepository } from "../../../test/repository/user"; -import { SystemInMemoryRepository } from "../../../test/repository/system"; import { CreateSystemUseCase, DeleteSystemUseCase, @@ -11,17 +9,20 @@ import { import { expectPreConditionalError } from "../../../test/utils/expectPreConditionalError"; import { SystemEntity } from "../entity/system"; import { MockGenerator } from "../../../test/utils/mockGenerator"; +import { UserRepositoryInterface } from "./repository/user"; +import { SystemRepositoryInterface } from "./repository/system"; +import { testFactory } from "../../../test/factory"; -let userRepository: UserInMemoryRepository; -let systemRepository: SystemInMemoryRepository; +let userRepository: UserRepositoryInterface; +let systemRepository: SystemRepositoryInterface; let mockGenerator: MockGenerator; describe("Create System Use Case", () => { let useCase: CreateSystemUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); - systemRepository = new SystemInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); + systemRepository = testFactory.makeSystemRepository(); useCase = new CreateSystemUseCase(systemRepository, userRepository); mockGenerator = new MockGenerator(userRepository, systemRepository); }); @@ -101,8 +102,8 @@ describe("List User Systems Use Case", () => { let useCase: ListSystemsByUserIdUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); - systemRepository = new SystemInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); + systemRepository = testFactory.makeSystemRepository(); useCase = new ListSystemsByUserIdUseCase(systemRepository, userRepository); mockGenerator = new MockGenerator(userRepository, systemRepository); }); @@ -169,7 +170,7 @@ describe("Get System Use Case", () => { let useCase: GetSystemUseCase; beforeEach(() => { - systemRepository = new SystemInMemoryRepository(); + systemRepository = testFactory.makeSystemRepository(); useCase = new GetSystemUseCase(systemRepository); mockGenerator = new MockGenerator(userRepository, systemRepository); }); @@ -202,7 +203,7 @@ describe("Update System Use Case", () => { let useCase: UpdateSystemUseCase; beforeEach(() => { - systemRepository = new SystemInMemoryRepository(); + systemRepository = testFactory.makeSystemRepository(); useCase = new UpdateSystemUseCase(systemRepository); mockGenerator = new MockGenerator(userRepository, systemRepository); }); @@ -333,7 +334,7 @@ describe("Delete System Use Case", () => { let useCase: DeleteSystemUseCase; beforeEach(() => { - systemRepository = new SystemInMemoryRepository(); + systemRepository = testFactory.makeSystemRepository(); useCase = new DeleteSystemUseCase(systemRepository); mockGenerator = new MockGenerator(userRepository, systemRepository); }); diff --git a/src/domain/usecase/system.ts b/src/domain/usecase/system.ts index d87402f..a43c925 100644 --- a/src/domain/usecase/system.ts +++ b/src/domain/usecase/system.ts @@ -1,5 +1,5 @@ -import * as systemValidateInterface from "../usecase/validate/system"; -import * as systemUcioInterface from "../usecase/ucio/system"; +import * as validate from "../usecase/validate/system"; +import * as ucio from "../usecase/ucio/system"; import { INTERNAL_SERVER_ERROR_MESSAGE, newInternalServerError, @@ -11,107 +11,102 @@ import { SystemRepositoryInterface } from "./repository/system"; import { UserRepositoryInterface } from "./repository/user"; class CreateSystemUseCase { - public validate: systemValidateInterface.CreateSystemUseCaseValidate; + public validate: validate.CreateSystemUseCaseValidate; public systemRepository: SystemRepositoryInterface; constructor( systemRepository: SystemRepositoryInterface, userRepository: UserRepositoryInterface, ) { - this.validate = new systemValidateInterface.CreateSystemUseCaseValidate( - userRepository, - ); + this.validate = new validate.CreateSystemUseCaseValidate(userRepository); this.systemRepository = systemRepository; } async execute( - req: systemUcioInterface.CreateSystemUseCaseRequest, - ): Promise { + req: ucio.CreateSystemUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { const systemResp = await this.systemRepository.createSystem(req); - - return new systemUcioInterface.CreateSystemUseCaseResponse( - systemResp, - null, - ); + return { + system: systemResp, + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new systemUcioInterface.CreateSystemUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + system: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new systemUcioInterface.CreateSystemUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + system: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class ListSystemsByUserIdUseCase { - public validate: systemValidateInterface.ListSystemsByUserIdUseCaseValidate; + public validate: validate.ListSystemsByUserIdUseCaseValidate; public systemRepository: SystemRepositoryInterface; constructor( systemRepository: SystemRepositoryInterface, userRepository: UserRepositoryInterface, ) { - this.validate = - new systemValidateInterface.ListSystemsByUserIdUseCaseValidate( - userRepository, - ); + this.validate = new validate.ListSystemsByUserIdUseCaseValidate( + userRepository, + ); this.systemRepository = systemRepository; } async execute( - req: systemUcioInterface.ListSystemsByUserIdUseCaseRequest, - ): Promise { + req: ucio.ListSystemsByUserIdUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { const systemsResp = await this.systemRepository.listSystemsByUserId(req); - - return new systemUcioInterface.ListSystemsByUserIdUseCaseResponse( - systemsResp, - null, - ); + return { + systems: systemsResp, + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new systemUcioInterface.ListSystemsByUserIdUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + systems: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new systemUcioInterface.ListSystemsByUserIdUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + systems: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class GetSystemUseCase { - public validate: systemValidateInterface.GetSystemUseCaseValidate; + public validate: validate.GetSystemUseCaseValidate; public systemRepository: SystemRepositoryInterface; constructor(systemRepository: SystemRepositoryInterface) { - this.validate = new systemValidateInterface.GetSystemUseCaseValidate(); + this.validate = new validate.GetSystemUseCaseValidate(); this.systemRepository = systemRepository; } async execute( - req: systemUcioInterface.GetSystemUseCaseRequest, - ): Promise { + req: ucio.GetSystemUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -119,94 +114,96 @@ class GetSystemUseCase { const system = await this.systemRepository.getSystem(req.id); if (system) { - return new systemUcioInterface.GetSystemUseCaseResponse(system, null); + return { + system, + error: null, + }; } else { - return new systemUcioInterface.GetSystemUseCaseResponse( - null, - newPreConditionalError("Sistema não encontrado"), - ); + return { + system: null, + error: newPreConditionalError("Sistema não encontrado"), + }; } } else { - return new systemUcioInterface.GetSystemUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + system: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new systemUcioInterface.GetSystemUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + system: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class DeleteSystemUseCase { - public validate: systemValidateInterface.DeleteSystemUseCaseValidate; + public validate: validate.DeleteSystemUseCaseValidate; public systemRepository: SystemRepositoryInterface; constructor(systemRepository: SystemRepositoryInterface) { - this.validate = new systemValidateInterface.DeleteSystemUseCaseValidate( - systemRepository, - ); + this.validate = new validate.DeleteSystemUseCaseValidate(systemRepository); this.systemRepository = systemRepository; } async execute( - req: systemUcioInterface.DeleteSystemUseCaseRequest, - ): Promise { + req: ucio.DeleteSystemUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { await this.systemRepository.deleteSystem(req); - - return new systemUcioInterface.DeleteSystemUseCaseResponse(null); + return { + error: null, + }; } else { - return new systemUcioInterface.DeleteSystemUseCaseResponse( - newPreConditionalError(messageError), - ); + return { + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new systemUcioInterface.DeleteSystemUseCaseResponse( - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class UpdateSystemUseCase { - public validate: systemValidateInterface.UpdateSystemUseCaseValidate; + public validate: validate.UpdateSystemUseCaseValidate; public systemRepository: SystemRepositoryInterface; constructor(systemRepository: SystemRepositoryInterface) { - this.validate = new systemValidateInterface.UpdateSystemUseCaseValidate( - systemRepository, - ); + this.validate = new validate.UpdateSystemUseCaseValidate(systemRepository); this.systemRepository = systemRepository; } async execute( - req: systemUcioInterface.UpdateSystemUseCaseRequest, - ): Promise { + req: ucio.UpdateSystemUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { await this.systemRepository.updateSystem(req); - - return new systemUcioInterface.UpdateSystemUseCaseResponse(null); + return { + error: null, + }; } else { - return new systemUcioInterface.UpdateSystemUseCaseResponse( - newPreConditionalError(messageError), - ); + return { + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(error); - return new systemUcioInterface.UpdateSystemUseCaseResponse( - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } diff --git a/src/domain/usecase/ucio/checklist.ts b/src/domain/usecase/ucio/checklist.ts index d65afcd..c4f4753 100644 --- a/src/domain/usecase/ucio/checklist.ts +++ b/src/domain/usecase/ucio/checklist.ts @@ -1,164 +1,64 @@ -import { Json } from "../../@types"; import { ChecklistEntity } from "../../entity/checklist"; -import { ErrorEntity } from "../../entity/error"; - -class CreateChecklistUseCaseRequest { - public tokenUserId: number; - public userId: number; - public systemId: number; - public checklistData: Json; - public isGeneral?: boolean; - public isIot?: boolean; - - constructor( - tokenUserId: number, - userId: number, - systemId: number, - checklistData: Json, - isGeneral?: boolean, - isIot?: boolean, - ) { - this.tokenUserId = tokenUserId; - this.userId = userId; - this.systemId = systemId; - this.checklistData = checklistData; - this.isGeneral = isGeneral; - this.isIot = isIot; - } -} - -class CreateChecklistUseCaseResponse { - public checklist: ChecklistEntity; - public error: ErrorEntity; - - constructor(checklist: ChecklistEntity, error: ErrorEntity) { - this.checklist = checklist; - this.error = error; - } -} - -class GetChecklistUseCaseRequest { - public tokenUserId: number; - public id: number; - - constructor(tokenUserId: number, id: number) { - this.tokenUserId = tokenUserId; - this.id = id; - } -} - -class GetChecklistUseCaseResponse { - public checklist: ChecklistEntity; - public error: ErrorEntity; - - constructor(checklist: ChecklistEntity, error: ErrorEntity) { - this.checklist = checklist; - this.error = error; - } -} - -class DeleteChecklistUseCaseRequest { - public tokenUserId: number; - public id: number; - - constructor(tokenUserId: number, id: number) { - this.tokenUserId = tokenUserId; - this.id = id; - } -} - -class DeleteChecklistUseCaseResponse { - public error: ErrorEntity; - - constructor(error: ErrorEntity) { - this.error = error; - } -} - -class UpdateChecklistUseCaseRequest { - public id: number; - public tokenUserId: number; - public systemId: number; - public checklistData: Json; - public isGeneral?: boolean; - public isIot?: boolean; - - constructor( - id: number, - tokenUserId: number, - systemId: number, - checklistData: Json, - isGeneral?: boolean, - isIot?: boolean, - ) { - this.id = id; - this.tokenUserId = tokenUserId; - this.systemId = systemId; - this.checklistData = checklistData; - this.isGeneral = isGeneral; - this.isIot = isIot; - } -} +import { AnswerType, SeverityDegreeType } from "../../entity/checklistItem"; +import { DeviceEntity } from "../../entity/device"; +import { LawEntity } from "../../entity/law"; +import { BaseResponse, UserAuthenticated } from "./common"; + +type ItemsInput = { + id: number; + answer: AnswerType; + severityDegree: SeverityDegreeType; + userComment?: string; +}[]; + +export type CreateChecklistUseCaseRequest = UserAuthenticated & { + userId: number; + systemId: number; + items: ItemsInput; + laws: LawEntity["id"][]; + devices: DeviceEntity["id"][]; +}; -class UpdateChecklistUseCaseResponse { - public error: ErrorEntity; +export type CreateChecklistUseCaseResponse = BaseResponse & { + checklist: ChecklistEntity; +}; - constructor(error: ErrorEntity) { - this.error = error; - } -} +export type GetChecklistUseCaseRequest = UserAuthenticated & { + id: number; +}; -class ListChecklistsByUserIdUseCaseRequest { - public tokenUserId: number; - public userId: number; +export type GetChecklistUseCaseResponse = BaseResponse & { + checklist: ChecklistEntity; +}; - constructor(tokenUserId: number, userId: number) { - this.tokenUserId = tokenUserId; - this.userId = userId; - } -} +export type DeleteChecklistUseCaseRequest = UserAuthenticated & { + id: number; +}; -class ListChecklistsByUserIdUseCaseResponse { - public checklists: ChecklistEntity[]; - public error: ErrorEntity; +export type DeleteChecklistUseCaseResponse = BaseResponse; - constructor(checklists: ChecklistEntity[], error: ErrorEntity) { - this.checklists = checklists; - this.error = error; - } -} +export type UpdateChecklistUseCaseRequest = UserAuthenticated & { + id: number; + systemId: number; + items: ItemsInput; + laws: LawEntity["id"][]; + devices: DeviceEntity["id"][]; +}; -class ListChecklistsBySystemIdUseCaseRequest { - public tokenUserId: number; - public systemId: number; +export type UpdateChecklistUseCaseResponse = BaseResponse; - constructor(tokenUserId: number, systemId: number) { - this.tokenUserId = tokenUserId; - this.systemId = systemId; - } -} +export type ListChecklistsByUserIdUseCaseRequest = UserAuthenticated & { + userId: number; +}; -class ListChecklistsBySystemIdUseCaseResponse { - public checklists: ChecklistEntity[]; - public error: ErrorEntity; +export type ListChecklistsByUserIdUseCaseResponse = BaseResponse & { + checklists: ChecklistEntity[]; +}; - constructor(checklists: ChecklistEntity[], error: ErrorEntity) { - this.checklists = checklists; - this.error = error; - } -} +export type ListChecklistsBySystemIdUseCaseRequest = UserAuthenticated & { + systemId: number; +}; -export { - CreateChecklistUseCaseRequest, - CreateChecklistUseCaseResponse, - GetChecklistUseCaseRequest, - GetChecklistUseCaseResponse, - DeleteChecklistUseCaseRequest, - DeleteChecklistUseCaseResponse, - UpdateChecklistUseCaseRequest, - UpdateChecklistUseCaseResponse, - ListChecklistsByUserIdUseCaseRequest, - ListChecklistsByUserIdUseCaseResponse, - ListChecklistsBySystemIdUseCaseRequest, - ListChecklistsBySystemIdUseCaseResponse, +export type ListChecklistsBySystemIdUseCaseResponse = BaseResponse & { + checklists: ChecklistEntity[]; }; diff --git a/src/domain/usecase/ucio/common.ts b/src/domain/usecase/ucio/common.ts new file mode 100644 index 0000000..b666acb --- /dev/null +++ b/src/domain/usecase/ucio/common.ts @@ -0,0 +1,9 @@ +import { ErrorEntity } from "../../entity/error"; + +export type BaseResponse = { + error: ErrorEntity; +}; + +export type UserAuthenticated = { + tokenUserId: number; +}; diff --git a/src/domain/usecase/ucio/device.ts b/src/domain/usecase/ucio/device.ts new file mode 100644 index 0000000..c9050d4 --- /dev/null +++ b/src/domain/usecase/ucio/device.ts @@ -0,0 +1,10 @@ +import { DeviceEntity } from "../../entity/device"; +import { BaseResponse } from "./common"; + +export type CreateDeviceUseCaseRequest = { + name: string; +}; + +export type ListDevicesUseCaseResponse = BaseResponse & { + devices: DeviceEntity[]; +}; diff --git a/src/domain/usecase/ucio/item.ts b/src/domain/usecase/ucio/item.ts new file mode 100644 index 0000000..92dacb6 --- /dev/null +++ b/src/domain/usecase/ucio/item.ts @@ -0,0 +1,23 @@ +import { DeviceEntity } from "../../entity/device"; +import { ItemEntity } from "../../entity/item"; +import { LawEntity } from "../../entity/law"; +import { BaseResponse } from "./common"; + +export type CreateItemUseCaseRequest = { + code: string; + itemDesc: string; + recommendations: string; + isMandatory: boolean; + sectionId: number; + lawsIds: number[]; + devicesIds: number[]; +}; + +export type ListItemsUseCaseRequest = { + laws: LawEntity["id"][]; + devices: DeviceEntity["id"][]; +}; + +export type ListItemsUseCaseResponse = BaseResponse & { + items: ItemEntity[]; +}; diff --git a/src/domain/usecase/ucio/law.ts b/src/domain/usecase/ucio/law.ts new file mode 100644 index 0000000..4dfcc7c --- /dev/null +++ b/src/domain/usecase/ucio/law.ts @@ -0,0 +1,10 @@ +import { LawEntity } from "../../entity/law"; +import { BaseResponse } from "./common"; + +export type CreateLawUseCaseRequest = { + name: string; +}; + +export type ListLawsUseCaseResponse = BaseResponse & { + laws: LawEntity[]; +}; diff --git a/src/domain/usecase/ucio/section.ts b/src/domain/usecase/ucio/section.ts new file mode 100644 index 0000000..feda538 --- /dev/null +++ b/src/domain/usecase/ucio/section.ts @@ -0,0 +1,3 @@ +export type CreateSectionUseCaseRequest = { + name: string; +}; diff --git a/src/domain/usecase/ucio/system.ts b/src/domain/usecase/ucio/system.ts index 090526a..0c4ab33 100644 --- a/src/domain/usecase/ucio/system.ts +++ b/src/domain/usecase/ucio/system.ts @@ -1,127 +1,42 @@ import { SystemEntity } from "../../entity/system"; -import { ErrorEntity } from "../../entity/error"; +import { BaseResponse, UserAuthenticated } from "./common"; -class CreateSystemUseCaseRequest { - public name: string; - public description: string; - public userId: number; - public tokenUserId: number; - - constructor( - name: string, - description: string, - userId: number, - tokenUserId: number, - ) { - this.name = name; - this.description = description; - this.userId = userId; - this.tokenUserId = tokenUserId; - } -} - -class CreateSystemUseCaseResponse { - public system: SystemEntity; - public error: ErrorEntity; - - constructor(system: SystemEntity, error: ErrorEntity) { - this.system = system; - this.error = error; - } -} - -class ListSystemsByUserIdUseCaseRequest { - public tokenUserId: number; - public userId: number; - - constructor(tokenUserId: number, userId: number) { - this.tokenUserId = tokenUserId; - this.userId = userId; - } -} - -class ListSystemsByUserIdUseCaseResponse { - public systems: SystemEntity[]; - public error: ErrorEntity; - - constructor(systems: SystemEntity[], error: ErrorEntity) { - this.systems = systems; - this.error = error; - } -} - -class GetSystemUseCaseRequest { - public id: number; - - constructor(id: number) { - this.id = id; - } -} - -class GetSystemUseCaseResponse { - public system: SystemEntity; - public error: ErrorEntity; - - constructor(system: SystemEntity, error: ErrorEntity) { - this.system = system; - this.error = error; - } -} - -class DeleteSystemUseCaseRequest { - public tokenUserId: number; - public id: number; +export type CreateSystemUseCaseRequest = UserAuthenticated & { + name: string; + description: string; + userId: number; +}; - constructor(id: number, tokenUserId: number) { - this.id = id; - this.tokenUserId = tokenUserId; - } -} +export type CreateSystemUseCaseResponse = BaseResponse & { + system: SystemEntity; +}; -class DeleteSystemUseCaseResponse { - public error: ErrorEntity; +export type ListSystemsByUserIdUseCaseRequest = UserAuthenticated & { + userId: number; +}; - constructor(error: ErrorEntity) { - this.error = error; - } -} +export type ListSystemsByUserIdUseCaseResponse = BaseResponse & { + systems: SystemEntity[]; +}; -class UpdateSystemUseCaseRequest { - public id: number; - public name: string; - public description: string; - public tokenUserId: number; +export type GetSystemUseCaseRequest = { + id: number; +}; - constructor( - id: number, - name: string, - description: string, - tokenUserId: number, - ) { - this.id = id; - this.name = name; - this.description = description; - this.tokenUserId = tokenUserId; - } -} +export type GetSystemUseCaseResponse = BaseResponse & { + system: SystemEntity; +}; -class UpdateSystemUseCaseResponse { - public error: ErrorEntity; +export type DeleteSystemUseCaseRequest = UserAuthenticated & { + id: number; +}; - constructor(error: ErrorEntity) { - this.error = error; - } -} +export type DeleteSystemUseCaseResponse = BaseResponse; -export { - CreateSystemUseCaseRequest, - CreateSystemUseCaseResponse, - ListSystemsByUserIdUseCaseRequest, - ListSystemsByUserIdUseCaseResponse, - GetSystemUseCaseRequest, - GetSystemUseCaseResponse, - DeleteSystemUseCaseRequest, - DeleteSystemUseCaseResponse, - UpdateSystemUseCaseRequest, - UpdateSystemUseCaseResponse, +export type UpdateSystemUseCaseRequest = UserAuthenticated & { + id: number; + name: string; + description: string; }; + +export type UpdateSystemUseCaseResponse = BaseResponse; diff --git a/src/domain/usecase/ucio/user.ts b/src/domain/usecase/ucio/user.ts index a966d76..a06f77f 100644 --- a/src/domain/usecase/ucio/user.ts +++ b/src/domain/usecase/ucio/user.ts @@ -1,141 +1,54 @@ -import { ErrorEntity } from "../../entity/error"; import { UserEntity } from "../../entity/user"; +import { BaseResponse, UserAuthenticated } from "./common"; -class CreateUserUseCaseRequest { - public name: string; - public office: string; - public email: string; - public password: string; - - constructor(name: string, office: string, email: string, password: string) { - this.name = name; - this.office = office; - this.email = email; - this.password = password; - } -} - -class CreateUserUseCaseResponse { - public user: UserEntity; - public error: ErrorEntity; - - constructor(user: UserEntity, error: ErrorEntity) { - this.user = user; - this.error = error; - } -} - -class LoginUseCaseRequest { - public email: string; - public password: string; - - constructor(email: string, password: string) { - this.email = email; - this.password = password; - } -} - -class LoginUseCaseResponse { - public user: UserEntity; - public token: string; - public error: ErrorEntity; - - constructor(user: UserEntity, token: string, error: ErrorEntity) { - this.user = user; - this.token = token; - this.error = error; - } -} - -class VerifyTokenUseCaseRequest { - public token: string; - - constructor(token: string) { - this.token = token; - } -} - -class VerifyTokenUseCaseResponse { - public user: UserEntity; - public token: string; - public error: ErrorEntity; - - constructor(user: UserEntity, token: string, error: ErrorEntity) { - this.user = user; - this.token = token; - this.error = error; - } -} - -class UpdateUserUseCaseRequest { - public tokenUserId: number; - public id: number; - public name: string; - public office: string; - - constructor(tokenUserId: number, id: number, name: string, office: string) { - this.tokenUserId = tokenUserId; - this.id = id; - this.name = name; - this.office = office; - } -} - -class UpdateUserUseCaseResponse { - public error: ErrorEntity; +export type CreateUserUseCaseRequest = { + name: string; + office: string; + email: string; + password: string; +}; - constructor(error: ErrorEntity) { - this.error = error; - } -} +export type CreateUserUseCaseResponse = BaseResponse & { + user: UserEntity; +}; -class GetUserUseCaseRequest { - public id: number; +export type LoginUseCaseRequest = { + email: string; + password: string; +}; - constructor(id: number) { - this.id = id; - } -} +export type LoginUseCaseResponse = BaseResponse & { + user: UserEntity; + token: string; +}; -class GetUserUseCaseResponse { - public user: UserEntity; - public error: ErrorEntity; +export type VerifyTokenUseCaseRequest = { + token: string; +}; - constructor(user: UserEntity, error: ErrorEntity) { - this.user = user; - this.error = error; - } -} +export type VerifyTokenUseCaseResponse = BaseResponse & { + user: UserEntity; + token: string; +}; -class DeleteUserUseCaseRequest { - public tokenUserId: number; - public id: number; +export type UpdateUserUseCaseRequest = UserAuthenticated & { + id: number; + name: string; + office: string; +}; - constructor(tokenUserId: number, id: number) { - this.tokenUserId = tokenUserId; - this.id = id; - } -} +export type UpdateUserUseCaseResponse = BaseResponse; -class DeleteUserUseCaseResponse { - public error: ErrorEntity; +export type GetUserUseCaseRequest = { + id: number; +}; - constructor(error: ErrorEntity) { - this.error = error; - } -} +export type GetUserUseCaseResponse = BaseResponse & { + user: UserEntity; +}; -export { - CreateUserUseCaseRequest, - CreateUserUseCaseResponse, - LoginUseCaseRequest, - LoginUseCaseResponse, - VerifyTokenUseCaseRequest, - VerifyTokenUseCaseResponse, - UpdateUserUseCaseRequest, - UpdateUserUseCaseResponse, - GetUserUseCaseRequest, - GetUserUseCaseResponse, - DeleteUserUseCaseRequest, - DeleteUserUseCaseResponse, +export type DeleteUserUseCaseRequest = UserAuthenticated & { + id: number; }; + +export type DeleteUserUseCaseResponse = BaseResponse; diff --git a/src/domain/usecase/user.spec.ts b/src/domain/usecase/user.spec.ts index 7de6043..23df993 100644 --- a/src/domain/usecase/user.spec.ts +++ b/src/domain/usecase/user.spec.ts @@ -8,22 +8,23 @@ import { VerifyTokenUseCase, } from "./user"; import bcrypt from "bcryptjs"; -import { UserInMemoryRepository } from "../../../test/repository/user"; -import { AuthFakeRepository } from "../../../test/repository/auth"; import { UserEntity } from "../entity/user"; import { expectPreConditionalError } from "../../../test/utils/expectPreConditionalError"; import { MockGenerator } from "../../../test/utils/mockGenerator"; +import { testFactory } from "../../../test/factory"; +import { UserRepositoryInterface } from "./repository/user"; +import { AuthRepositoryInterface } from "./repository/auth"; const { compareSync } = bcrypt; -let userRepository: UserInMemoryRepository; -let authRepository: AuthFakeRepository; +let userRepository: UserRepositoryInterface; +let authRepository: AuthRepositoryInterface; let mockGenerator: MockGenerator; describe("Create User Use Case", () => { let useCase: CreateUserUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); useCase = new CreateUserUseCase(userRepository); }); @@ -96,8 +97,8 @@ describe("Login Use Case", () => { let useCase: LoginUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); - authRepository = new AuthFakeRepository(); + userRepository = testFactory.makeUserRepository(); + authRepository = testFactory.makeAuthRepository(); useCase = new LoginUseCase(userRepository, authRepository); mockGenerator = new MockGenerator(userRepository); }); @@ -154,8 +155,8 @@ describe("Verify Token Use Case", () => { let useCase: VerifyTokenUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); - authRepository = new AuthFakeRepository(); + userRepository = testFactory.makeUserRepository(); + authRepository = testFactory.makeAuthRepository(); useCase = new VerifyTokenUseCase(userRepository, authRepository); mockGenerator = new MockGenerator(userRepository); }); @@ -194,7 +195,7 @@ describe("Update User Use Case", () => { let useCase: UpdateUserUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); useCase = new UpdateUserUseCase(userRepository); mockGenerator = new MockGenerator(userRepository); }); @@ -315,7 +316,7 @@ describe("Get User Use Case", () => { let useCase: GetUserUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); useCase = new GetUserUseCase(userRepository); mockGenerator = new MockGenerator(userRepository); }); @@ -348,7 +349,7 @@ describe("Delete User Use Case", () => { let useCase: DeleteUserUseCase; beforeEach(() => { - userRepository = new UserInMemoryRepository(); + userRepository = testFactory.makeUserRepository(); useCase = new DeleteUserUseCase(userRepository); mockGenerator = new MockGenerator(userRepository); }); diff --git a/src/domain/usecase/user.ts b/src/domain/usecase/user.ts index 028af4a..2ab145d 100644 --- a/src/domain/usecase/user.ts +++ b/src/domain/usecase/user.ts @@ -1,5 +1,5 @@ -import * as userValidateInterface from "./validate/user"; -import * as userUcioInterface from "./ucio/user"; +import * as validate from "./validate/user"; +import * as ucio from "./ucio/user"; import bcrypt from "bcryptjs"; import { INTERNAL_SERVER_ERROR_MESSAGE, @@ -13,19 +13,17 @@ import { AuthRepositoryInterface } from "./repository/auth"; const { genSaltSync, hashSync } = bcrypt; class CreateUserUseCase { - public validate: userValidateInterface.CreateUserUseCaseValidate; + public validate: validate.CreateUserUseCaseValidate; public userRepository: UserRepositoryInterface; constructor(userRepository: UserRepositoryInterface) { - this.validate = new userValidateInterface.CreateUserUseCaseValidate( - userRepository, - ); + this.validate = new validate.CreateUserUseCaseValidate(userRepository); this.userRepository = userRepository; } async execute( - req: userUcioInterface.CreateUserUseCaseRequest, - ): Promise { + req: ucio.CreateUserUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -36,26 +34,31 @@ class CreateUserUseCase { const userResp = await this.userRepository.createUser(req); - return new userUcioInterface.CreateUserUseCaseResponse(userResp, null); + return { + user: userResp, + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new userUcioInterface.CreateUserUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + + return { + user: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new userUcioInterface.CreateUserUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + user: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class LoginUseCase { - public validate: userValidateInterface.LoginUseCaseValidate; + public validate: validate.LoginUseCaseValidate; public userRepository: UserRepositoryInterface; public authRepository: AuthRepositoryInterface; @@ -63,14 +66,14 @@ class LoginUseCase { userRepository: UserRepositoryInterface, authRepository: AuthRepositoryInterface, ) { - this.validate = new userValidateInterface.LoginUseCaseValidate(); + this.validate = new validate.LoginUseCaseValidate(); this.userRepository = userRepository; this.authRepository = authRepository; } async execute( - req: userUcioInterface.LoginUseCaseRequest, - ): Promise { + req: ucio.LoginUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -79,43 +82,43 @@ class LoginUseCase { if (userResp) { const token = this.authRepository.createToken(userResp.id); - - return new userUcioInterface.LoginUseCaseResponse( - userResp, + return { + user: userResp, token, - null, - ); + error: null, + }; } console.log( `${TAG_PRE_CONDITIONAL_ERROR} E-mail e/ou Senha incorretos.`, ); - return new userUcioInterface.LoginUseCaseResponse( - null, - null, - newPreConditionalError("E-mail e/ou Senha incorretos."), - ); + return { + user: null, + token: null, + error: newPreConditionalError("E-mail e/ou Senha incorretos."), + }; } console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new userUcioInterface.LoginUseCaseResponse( - null, - null, - newPreConditionalError(messageError), - ); + return { + user: null, + token: null, + error: newPreConditionalError(messageError), + }; } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new userUcioInterface.LoginUseCaseResponse( - null, - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + + return { + user: null, + token: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class VerifyTokenUseCase { - public validate: userValidateInterface.VerifyTokenUseCaseValidate; + public validate: validate.VerifyTokenUseCaseValidate; public userRepository: UserRepositoryInterface; public authRepository: AuthRepositoryInterface; @@ -123,14 +126,14 @@ class VerifyTokenUseCase { userRepository: UserRepositoryInterface, authRepository: AuthRepositoryInterface, ) { - this.validate = new userValidateInterface.VerifyTokenUseCaseValidate(); + this.validate = new validate.VerifyTokenUseCaseValidate(); this.userRepository = userRepository; this.authRepository = authRepository; } async execute( - req: userUcioInterface.VerifyTokenUseCaseRequest, - ): Promise { + req: ucio.VerifyTokenUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -139,11 +142,11 @@ class VerifyTokenUseCase { if (typeof userIsValid === "string") { console.log(`${TAG_PRE_CONDITIONAL_ERROR} Sua sessão expirou`); - return new userUcioInterface.VerifyTokenUseCaseResponse( - null, - null, - newPreConditionalError("Sua sessão expirou"), - ); + return { + user: null, + token: null, + error: newPreConditionalError("Sua sessão expirou"), + }; } const user = await this.userRepository.getUser(userIsValid.id); @@ -151,88 +154,87 @@ class VerifyTokenUseCase { if (user) { const newToken = await this.authRepository.createToken(user.id); - return new userUcioInterface.VerifyTokenUseCaseResponse( + return { user, - newToken, - null, - ); + token: newToken, + error: null, + }; } else { console.log( `${TAG_PRE_CONDITIONAL_ERROR} Houve um erro com sua sessão, por favor, faça login novamente`, ); - return new userUcioInterface.VerifyTokenUseCaseResponse( - null, - null, - newPreConditionalError( + return { + user: null, + token: null, + error: newPreConditionalError( "Houve um erro com sua sessão, por favor, faça login novamente", ), - ); + }; } } else { - return new userUcioInterface.VerifyTokenUseCaseResponse( - null, - null, - newPreConditionalError(messageError), - ); + return { + user: null, + token: null, + error: newPreConditionalError(messageError), + }; } } catch (err) { - return new userUcioInterface.VerifyTokenUseCaseResponse( - null, - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + user: null, + token: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class UpdateUserUseCase { - public validate: userValidateInterface.UpdateUserUseCaseValidate; + public validate: validate.UpdateUserUseCaseValidate; public userRepository: UserRepositoryInterface; constructor(userRepository: UserRepositoryInterface) { - this.validate = new userValidateInterface.UpdateUserUseCaseValidate( - userRepository, - ); + this.validate = new validate.UpdateUserUseCaseValidate(userRepository); this.userRepository = userRepository; } async execute( - req: userUcioInterface.UpdateUserUseCaseRequest, - ): Promise { + req: ucio.UpdateUserUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { await this.userRepository.updateUser(req); - - return new userUcioInterface.UpdateUserUseCaseResponse(null); + return { + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new userUcioInterface.UpdateUserUseCaseResponse( - newPreConditionalError(messageError), - ); + return { + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new userUcioInterface.UpdateUserUseCaseResponse( - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class GetUserUseCase { - public validate: userValidateInterface.GetUserUseCaseValidate; + public validate: validate.GetUserUseCaseValidate; public userRepository: UserRepositoryInterface; constructor(userRepository: UserRepositoryInterface) { - this.validate = new userValidateInterface.GetUserUseCaseValidate(); + this.validate = new validate.GetUserUseCaseValidate(); this.userRepository = userRepository; } async execute( - req: userUcioInterface.GetUserUseCaseRequest, - ): Promise { + req: ucio.GetUserUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); @@ -240,63 +242,66 @@ class GetUserUseCase { const user = await this.userRepository.getUser(req.id); if (user) { - return new userUcioInterface.GetUserUseCaseResponse(user, null); + return { + user, + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} Usuário não encontrado`); - return new userUcioInterface.GetUserUseCaseResponse( + return { user, - newPreConditionalError("Usuário não encontrado"), - ); + error: newPreConditionalError("Usuário não encontrado"), + }; } } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new userUcioInterface.GetUserUseCaseResponse( - null, - newPreConditionalError(messageError), - ); + return { + user: null, + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new userUcioInterface.GetUserUseCaseResponse( - null, - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + user: null, + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } class DeleteUserUseCase { - public validate: userValidateInterface.DeleteUserUseCaseValidate; + public validate: validate.DeleteUserUseCaseValidate; public userRepository: UserRepositoryInterface; constructor(userRepository: UserRepositoryInterface) { - this.validate = new userValidateInterface.DeleteUserUseCaseValidate( - userRepository, - ); + this.validate = new validate.DeleteUserUseCaseValidate(userRepository); this.userRepository = userRepository; } async execute( - req: userUcioInterface.DeleteUserUseCaseRequest, - ): Promise { + req: ucio.DeleteUserUseCaseRequest, + ): Promise { try { const messageError = await this.validate.validate(req); if (!messageError) { await this.userRepository.deleteUser(req); - return new userUcioInterface.DeleteUserUseCaseResponse(null); + return { + error: null, + }; } else { console.log(`${TAG_PRE_CONDITIONAL_ERROR} ${messageError}`); - return new userUcioInterface.DeleteUserUseCaseResponse( - newPreConditionalError(messageError), - ); + return { + error: newPreConditionalError(messageError), + }; } } catch (error) { console.log(`${TAG_INTERNAL_SERVER_ERROR} ${error}`); - return new userUcioInterface.DeleteUserUseCaseResponse( - newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), - ); + return { + error: newInternalServerError(INTERNAL_SERVER_ERROR_MESSAGE), + }; } } } diff --git a/src/domain/usecase/validate/checklist.ts b/src/domain/usecase/validate/checklist.ts index a75b03c..1f63399 100644 --- a/src/domain/usecase/validate/checklist.ts +++ b/src/domain/usecase/validate/checklist.ts @@ -12,36 +12,69 @@ import { import { ChecklistRepositoryInterface } from "../repository/checklist"; import { SystemRepositoryInterface } from "../repository/system"; import { UserRepositoryInterface } from "../repository/user"; +import { validateWithZod, zodNumberSchema, zodStringSchema } from "./utils"; import { - isNonEmptyJson, - validateWithZod, - zodBooleanSchema, - zodNumberSchema, -} from "./utils"; -import { Json } from "../../@types"; + answerTypeArray, + severityDegreeTypeArray, +} from "../../entity/checklistItem"; +import { ItemRepositoryInterface } from "../repository/item"; +import { LawRepositoryInterface } from "../repository/law"; +import { DeviceRepositoryInterface } from "../repository/device"; + +const itemsZodValidation = z + .object({ + id: zodNumberSchema("Id do item"), + answer: z.enum(answerTypeArray).optional(), + severityDegree: z.enum(severityDegreeTypeArray).optional(), + userComment: zodStringSchema("userComment").optional(), + }) + .refine( + (item) => { + return !( + item.answer === "Não" && !(item.severityDegree && item.userComment) + ); + }, + { + message: + "Para resposta não, é obrigatório ter grau de severidade e comentário do usuário", + path: ["answer"], + }, + ) + .array() + .nonempty({ + message: "Items não pode ser um array vazio.", + }); class CreateChecklistUseCaseValidate implements ValidateInterface { private systemRepository: SystemRepositoryInterface; private userRepository: UserRepositoryInterface; + private itemRepository: ItemRepositoryInterface; + private lawRepository: LawRepositoryInterface; + private deviceRepository: DeviceRepositoryInterface; + private validationSchema = z.object({ userId: zodNumberSchema("UserId"), systemId: zodNumberSchema("SystemId"), tokenUserId: zodNumberSchema("Id do token"), - isGeneral: zodBooleanSchema("isGeneral").refine((val) => val === true, { - message: "isGeneral não pode ser falso.", - }), - isIot: zodBooleanSchema("isIot"), - checklistData: z.custom(isNonEmptyJson, { - message: "checklistData não podem ser vazio e deve estar no formato JSON", + items: itemsZodValidation, + laws: zodNumberSchema("laws").array().nonempty({ + message: "Laws não pode ser um array vazio.", }), + devices: zodNumberSchema("devices").array(), }); constructor( systemRepository: SystemRepositoryInterface, userRepository: UserRepositoryInterface, + itemRepository: ItemRepositoryInterface, + lawRepository: LawRepositoryInterface, + deviceRepository: DeviceRepositoryInterface, ) { this.systemRepository = systemRepository; this.userRepository = userRepository; + this.itemRepository = itemRepository; + this.lawRepository = lawRepository; + this.deviceRepository = deviceRepository; } async validate(req: CreateChecklistUseCaseRequest): Promise { @@ -54,16 +87,30 @@ class CreateChecklistUseCaseValidate implements ValidateInterface { const system = await this.systemRepository.getSystem(req.systemId); - if (!system) { - return "O sistema informado não existe."; - } + const items = await this.itemRepository.itemsExistByIds( + req.items.map((i) => i.id), + ); - if ( - req.tokenUserId !== req.userId || - system.userId !== req.tokenUserId - ) { + if (items.length) + return "Os seguintes ids de item não existem: " + items.join(", "); + + const laws = await this.lawRepository.existByIds(req.laws); + + if (laws.length) + return "Os seguintes ids de leis não existem: " + laws.join(", "); + + const devices = await this.deviceRepository.existByIds(req.devices); + + if (devices.length) + return ( + "Os seguintes ids de dispositivos não existem: " + + devices.join(", ") + ); + + if (!system) return "O sistema informado não existe."; + + if (req.tokenUserId !== req.userId || system.userId !== req.tokenUserId) return NO_PERMISSION_MESSAGE; - } return null; }, @@ -136,25 +183,33 @@ class DeleteChecklistUseCaseValidate implements ValidateInterface { class UpdateChecklistUseCaseValidate implements ValidateInterface { private checklistRepository: ChecklistRepositoryInterface; private systemRepository: SystemRepositoryInterface; + private itemRepository: ItemRepositoryInterface; + private lawRepository: LawRepositoryInterface; + private deviceRepository: DeviceRepositoryInterface; + private validationSchema = z.object({ id: zodNumberSchema("Id"), systemId: zodNumberSchema("SystemId"), tokenUserId: zodNumberSchema("Id do token"), - isGeneral: zodBooleanSchema("isGeneral").refine((val) => val === true, { - message: "isGeneral não pode ser falso.", - }), - isIot: zodBooleanSchema("isIot"), - checklistData: z.custom(isNonEmptyJson, { - message: "checklistData não podem ser vazio e deve ser no formato JSON", + items: itemsZodValidation, + laws: zodNumberSchema("laws").array().nonempty({ + message: "Laws não pode ser um array vazio.", }), + devices: zodNumberSchema("devices").array(), }); constructor( checklistRepository: ChecklistRepositoryInterface, systemRepository: SystemRepositoryInterface, + itemRepository: ItemRepositoryInterface, + lawRepository: LawRepositoryInterface, + deviceRepository: DeviceRepositoryInterface, ) { this.checklistRepository = checklistRepository; this.systemRepository = systemRepository; + this.itemRepository = itemRepository; + this.lawRepository = lawRepository; + this.deviceRepository = deviceRepository; } async validate(req: UpdateChecklistUseCaseRequest): Promise { @@ -173,6 +228,26 @@ class UpdateChecklistUseCaseValidate implements ValidateInterface { return "O sistema não foi encontrado."; } + const items = await this.itemRepository.itemsExistByIds( + req.items.map((i) => i.id), + ); + + if (items.length) + return "Os seguintes ids de item não existem: " + items.join(", "); + + const laws = await this.lawRepository.existByIds(req.laws); + + if (laws.length) + return "Os seguintes ids de leis não existem: " + laws.join(", "); + + const devices = await this.deviceRepository.existByIds(req.devices); + + if (devices.length) + return ( + "Os seguintes ids de dispositivos não existem: " + + devices.join(", ") + ); + if ( checklist.userId !== req.tokenUserId || system.userId !== req.tokenUserId diff --git a/src/domain/usecase/validate/item.ts b/src/domain/usecase/validate/item.ts new file mode 100644 index 0000000..8a5b442 --- /dev/null +++ b/src/domain/usecase/validate/item.ts @@ -0,0 +1,47 @@ +import * as z from "zod"; +import { validateWithZod, zodNumberSchema } from "./utils"; +import { ValidateInterface } from "."; +import { ListItemsUseCaseRequest } from "../ucio/item"; +import { LawRepositoryInterface } from "../repository/law"; +import { DeviceRepositoryInterface } from "../repository/device"; + +export class ListItemsUseCaseValidate implements ValidateInterface { + private lawRepository: LawRepositoryInterface; + private deviceRepository: DeviceRepositoryInterface; + private validationSchema = z.object({ + laws: zodNumberSchema("laws").array().nonempty({ + message: "Laws não pode ser um array vazio.", + }), + devices: zodNumberSchema("devices").array(), + }); + + constructor( + lawRepository: LawRepositoryInterface, + deviceRepository: DeviceRepositoryInterface, + ) { + this.lawRepository = lawRepository; + this.deviceRepository = deviceRepository; + } + + async validate(req: ListItemsUseCaseRequest): Promise { + return await validateWithZod( + () => this.validationSchema.parse(req), + async () => { + const laws = await this.lawRepository.existByIds(req.laws); + + if (laws.length) + return "Os seguintes ids de leis não existem: " + laws.join(", "); + + const devices = await this.deviceRepository.existByIds(req.devices); + + if (devices.length) + return ( + "Os seguintes ids de dispositivos não existem: " + + devices.join(", ") + ); + + return null; + }, + ); + } +} diff --git a/src/domain/usecase/validate/utils/index.ts b/src/domain/usecase/validate/utils/index.ts index ce3cc81..5004b56 100644 --- a/src/domain/usecase/validate/utils/index.ts +++ b/src/domain/usecase/validate/utils/index.ts @@ -1,4 +1,3 @@ -import { Json } from "../../../@types"; import * as z from "zod"; import { DEFAULT_VALIDATION_MESSAGE } from "../../../entity/error"; @@ -96,25 +95,4 @@ const validateWithZod = async ( } }; -const isNonEmptyJson = (val: unknown): val is Json => { - try { - const parsed = JSON.parse(JSON.stringify(val)); - return ( - typeof parsed === "object" && - parsed !== null && - (Array.isArray(parsed) - ? parsed.length > 0 - : Object.keys(parsed).length > 0) - ); - } catch (e) { - return false; - } -}; - -export { - zodStringSchema, - zodNumberSchema, - zodBooleanSchema, - validateWithZod, - isNonEmptyJson, -}; +export { zodStringSchema, zodNumberSchema, zodBooleanSchema, validateWithZod }; diff --git a/src/infrastructure/internal/connection/prisma.ts b/src/infrastructure/internal/connection/prisma.ts deleted file mode 100644 index a031944..0000000 --- a/src/infrastructure/internal/connection/prisma.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PrismaClient } from "@prisma/client"; - -const prisma = new PrismaClient(); - -export { prisma }; diff --git a/src/infrastructure/internal/database/postgresql/checklist.ts b/src/infrastructure/internal/database/postgresql/checklist.ts deleted file mode 100644 index e1f058b..0000000 --- a/src/infrastructure/internal/database/postgresql/checklist.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Checklists } from "@prisma/client"; -import { ChecklistEntity } from "../../../../domain/entity/checklist"; -import { - CreateChecklistUseCaseRequest, - UpdateChecklistUseCaseRequest, -} from "../../../../domain/usecase/ucio/checklist"; -import { prisma } from "../../connection/prisma"; - -async function createChecklist( - req: CreateChecklistUseCaseRequest, -): Promise { - const checklist = await prisma.checklists.create({ - data: { - userId: req.userId, - systemId: req.systemId, - isGeneral: req.isGeneral, - isIot: req.isIot, - checklistData: req.checklistData, - }, - }); - - return new ChecklistEntity( - checklist.id, - checklist.userId, - checklist.systemId, - checklist.checklistData, - checklist.isGeneral, - checklist.isIot, - ); -} - -async function getChecklist(id: number): Promise { - const checklist = await prisma.checklists.findUnique({ - where: { - id, - }, - }); - - return checklist - ? new ChecklistEntity( - checklist.id, - checklist.userId, - checklist.systemId, - checklist.checklistData, - checklist.isGeneral, - checklist.isIot, - checklist.createdAt, - checklist.updatedAt, - ) - : null; -} - -async function deleteChecklist(id: number): Promise { - await prisma.checklists.delete({ - where: { - id, - }, - }); -} - -async function updateChecklist( - req: UpdateChecklistUseCaseRequest, -): Promise { - await prisma.checklists.update({ - where: { - id: req.id, - }, - data: { - systemId: req.systemId, - checklistData: req.checklistData, - isGeneral: req.isGeneral, - isIot: req.isIot, - }, - }); -} - -async function listChecklistsByUserId( - userId: number, -): Promise { - const checklists = await prisma.checklists.findMany({ - where: { - userId, - }, - orderBy: { - updatedAt: "desc", - }, - }); - - return checklists.map( - (checklist: Checklists) => - new ChecklistEntity( - checklist.id, - checklist.userId, - checklist.systemId, - null, - checklist.isGeneral, - checklist.isIot, - checklist.createdAt, - checklist.updatedAt, - ), - ); -} - -async function listChecklistsBySystemId( - systemId: number, -): Promise { - const checklists = await prisma.checklists.findMany({ - where: { - systemId, - }, - orderBy: { - updatedAt: "desc", - }, - }); - - return checklists.map( - (checklist: Checklists) => - new ChecklistEntity( - checklist.id, - checklist.userId, - checklist.systemId, - null, - checklist.isGeneral, - checklist.isIot, - checklist.createdAt, - checklist.updatedAt, - ), - ); -} - -export { - createChecklist, - getChecklist, - deleteChecklist, - updateChecklist, - listChecklistsByUserId, - listChecklistsBySystemId, -}; diff --git a/src/infrastructure/internal/database/postgresql/system.ts b/src/infrastructure/internal/database/postgresql/system.ts deleted file mode 100644 index c6fd47a..0000000 --- a/src/infrastructure/internal/database/postgresql/system.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Systems } from "@prisma/client"; -import { SystemEntity } from "@/domain/entity/system"; -import { prisma } from "../../connection/prisma"; -import { - CreateSystemUseCaseRequest, - UpdateSystemUseCaseRequest, -} from "../../../../domain/usecase/ucio/system"; - -async function createSystem( - req: CreateSystemUseCaseRequest, -): Promise { - const system = await prisma.systems.create({ - data: { - name: req.name, - description: req.description, - userId: req.userId, - }, - }); - - return new SystemEntity( - system.id, - system.name, - system.description, - system.userId, - ); -} - -async function listSystemsByUserId(userId: number): Promise { - const systems = await prisma.systems.findMany({ - where: { - userId, - }, - orderBy: { - updatedAt: "desc", - }, - }); - - return systems.map( - (system: Systems) => - new SystemEntity( - system.id, - system.name, - system.description, - system.userId, - ), - ); -} - -async function getSystem(id: number): Promise { - const system = await prisma.systems.findUnique({ - where: { - id, - }, - }); - - return system - ? new SystemEntity( - system.id, - system.name, - system.description, - system.userId, - ) - : null; -} - -async function deleteSystem(id: number): Promise { - await prisma.systems.delete({ - where: { - id, - }, - }); -} - -async function updateSystem(req: UpdateSystemUseCaseRequest): Promise { - await prisma.systems.update({ - where: { - id: req.id, - }, - data: { - name: req.name, - description: req.description, - }, - }); -} - -export { - createSystem, - getSystem, - listSystemsByUserId, - deleteSystem, - updateSystem, -}; diff --git a/src/infrastructure/internal/database/postgresql/user.ts b/src/infrastructure/internal/database/postgresql/user.ts deleted file mode 100644 index afa7011..0000000 --- a/src/infrastructure/internal/database/postgresql/user.ts +++ /dev/null @@ -1,100 +0,0 @@ -import bcrypt from "bcryptjs"; -import { UserEntity } from "../../../../domain/entity/user"; -import { - CreateUserUseCaseRequest, - LoginUseCaseRequest, - UpdateUserUseCaseRequest, -} from "../../../../domain/usecase/ucio/user"; -import { prisma } from "../../connection/prisma"; -const { compareSync } = bcrypt; - -async function createUser(data: CreateUserUseCaseRequest): Promise { - const user = await prisma.users.create({ - data: { - name: data.name, - office: data.office, - email: data.email, - password: data.password, - }, - }); - - return user - ? new UserEntity(user.id, user.name, user.office, user.email, null) - : null; -} - -async function getUser(id: number): Promise { - const user = await prisma.users.findFirst({ - where: { - id, - }, - }); - - return user - ? new UserEntity(user.id, user.name, user.office, user.email, null) - : null; -} - -async function updateUser(req: UpdateUserUseCaseRequest): Promise { - const user = await prisma.users.update({ - where: { - id: req.id, - }, - data: { - name: req.name, - office: req.office, - }, - }); - - return user - ? new UserEntity(user.id, user.name, user.office, user.email, null) - : null; -} - -async function deleteUser(id: number): Promise { - await prisma.users.delete({ - where: { - id, - }, - }); -} - -async function checkUserByEmailExists( - email: string, - id?: number, -): Promise { - const user = await prisma.users.findFirst({ - where: { - email, - }, - }); - - return user && user.id !== id; -} - -async function login(req: LoginUseCaseRequest): Promise { - const user = await prisma.users.findFirst({ - where: { - email: req.email, - }, - }); - - if (user && compareSync(req.password, user.password)) { - delete user.password; - - return user - ? new UserEntity(user.id, user.name, user.office, user.email, null) - : null; - } - - return null; -} - -export { - createUser, - checkUserByEmailExists, - login, - getUser, - updateUser, - deleteUser, -}; diff --git a/src/infrastructure/provider/repository/checklist.ts b/src/infrastructure/provider/repository/checklist.ts deleted file mode 100644 index f9e33c4..0000000 --- a/src/infrastructure/provider/repository/checklist.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as checklistService from "@/internal/database/postgresql/checklist"; -import { ChecklistEntity } from "../../../domain/entity/checklist"; -import { - CreateChecklistUseCaseRequest, - DeleteChecklistUseCaseRequest, - ListChecklistsBySystemIdUseCaseRequest, - ListChecklistsByUserIdUseCaseRequest, - UpdateChecklistUseCaseRequest, -} from "../../../domain/usecase/ucio/checklist"; -import { ChecklistRepositoryInterface } from "../../../domain/usecase/repository/checklist"; - -class ChecklistPrismaRepository implements ChecklistRepositoryInterface { - async createChecklist( - req: CreateChecklistUseCaseRequest, - ): Promise { - return await checklistService.createChecklist(req); - } - - async getChecklist(id: number): Promise { - return await checklistService.getChecklist(id); - } - - async deleteChecklist(req: DeleteChecklistUseCaseRequest): Promise { - await checklistService.deleteChecklist(req.id); - } - - async updateChecklist(req: UpdateChecklistUseCaseRequest): Promise { - return await checklistService.updateChecklist(req); - } - - async listChecklistsByUserId( - req: ListChecklistsByUserIdUseCaseRequest, - ): Promise { - return await checklistService.listChecklistsByUserId(req.userId); - } - - async listChecklistsBySystemId( - req: ListChecklistsBySystemIdUseCaseRequest, - ): Promise { - return await checklistService.listChecklistsBySystemId(req.systemId); - } -} - -export { ChecklistPrismaRepository }; diff --git a/src/infrastructure/provider/repository/prisma/checklist.ts b/src/infrastructure/provider/repository/prisma/checklist.ts new file mode 100644 index 0000000..7b728d8 --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/checklist.ts @@ -0,0 +1,394 @@ +import { ChecklistEntity } from "../../../../domain/entity/checklist"; +import { + CreateChecklistUseCaseRequest, + DeleteChecklistUseCaseRequest, + ListChecklistsBySystemIdUseCaseRequest, + ListChecklistsByUserIdUseCaseRequest, + UpdateChecklistUseCaseRequest, +} from "../../../../domain/usecase/ucio/checklist"; +import { ChecklistRepositoryInterface } from "../../../../domain/usecase/repository/checklist"; +import { + AnswerType, + ChecklistItemEntity, + SeverityDegreeType, +} from "../../../../domain/entity/checklistItem"; +import { ItemEntity } from "../../../../domain/entity/item"; +import { LawEntity } from "../../../../domain/entity/law"; +import { DeviceEntity } from "../../../../domain/entity/device"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; +import { SectionEntity } from "../../../../domain/entity/section"; + +class ChecklistPrismaRepository + extends PrismaRepository + implements ChecklistRepositoryInterface +{ + items?: ChecklistEntity[]; + + protected withTransaction(tx: Prisma.TransactionClient): this { + return new ChecklistPrismaRepository(tx) as this; + } + + async createChecklist( + req: CreateChecklistUseCaseRequest, + ): Promise { + const checklist = await this.prisma.checklists.create({ + data: { + userId: req.userId, + systemId: req.systemId, + ItemsChecklists: { + createMany: { + data: req.items.map((item) => { + return { + itemId: item.id, + answer: item.answer, + severityDegree: item.severityDegree, + userComment: item.userComment, + }; + }), + }, + }, + laws: { + connect: req.laws.map((id) => ({ id })), + }, + devices: { + connect: req.devices.map((id) => ({ id })), + }, + }, + include: { + ItemsChecklists: { + include: { + item: { + include: { + section: true, + }, + }, + }, + }, + laws: true, + devices: true, + }, + }); + + return new ChecklistEntity( + checklist.id, + checklist.userId, + checklist.systemId, + checklist.ItemsChecklists.map( + (itemChecklist) => + new ChecklistItemEntity( + null, + new ItemEntity( + itemChecklist.item.id, + itemChecklist.item.code, + itemChecklist.item.itemDesc, + itemChecklist.item.recommendations, + itemChecklist.item.isMandatory, + itemChecklist.item.sectionId, + new SectionEntity( + itemChecklist.item.section.id, + itemChecklist.item.section.name, + ), + ), + itemChecklist.answer as AnswerType, + itemChecklist.severityDegree as SeverityDegreeType, + itemChecklist.userComment, + ), + ), + checklist.laws.map((law) => new LawEntity(law.id, law.name)), + checklist.devices.map( + (device) => new DeviceEntity(device.id, device.name), + ), + ); + } + + async getChecklist(id: number): Promise { + const checklist = await this.prisma.checklists.findUnique({ + where: { + id, + }, + include: { + ItemsChecklists: { + include: { + item: { + include: { + devices: true, + laws: true, + section: true, + }, + }, + }, + }, + laws: true, + devices: true, + }, + }); + + return checklist + ? new ChecklistEntity( + checklist.id, + checklist.userId, + checklist.systemId, + checklist.ItemsChecklists.map( + (itemChecklist) => + new ChecklistItemEntity( + null, + new ItemEntity( + itemChecklist.item.id, + itemChecklist.item.code, + itemChecklist.item.itemDesc, + itemChecklist.item.recommendations, + itemChecklist.item.isMandatory, + itemChecklist.item.sectionId, + new SectionEntity( + itemChecklist.item.section.id, + itemChecklist.item.section.name, + ), + itemChecklist.item.laws.map( + (law) => new LawEntity(law.id, law.name), + ), + itemChecklist.item.devices.map( + (dev) => new DeviceEntity(dev.id, dev.name), + ), + ), + itemChecklist.answer as AnswerType, + itemChecklist.severityDegree as SeverityDegreeType, + itemChecklist.userComment, + ), + ), + checklist.laws.map((law) => new LawEntity(law.id, law.name)), + checklist.devices.map( + (device) => new DeviceEntity(device.id, device.name), + ), + checklist.createdAt, + checklist.updatedAt, + ) + : null; + } + + async deleteChecklist(req: DeleteChecklistUseCaseRequest): Promise { + await this.prisma.checklists.delete({ + where: { + id: req.id, + }, + }); + } + + async updateChecklist(req: UpdateChecklistUseCaseRequest): Promise { + await this.prisma.checklists.update({ + where: { + id: req.id, + }, + data: { + systemId: req.systemId, + }, + }); + } + + async listChecklistsByUserId( + req: ListChecklistsByUserIdUseCaseRequest, + ): Promise { + const checklists = await this.prisma.checklists.findMany({ + where: { + userId: req.userId, + }, + orderBy: { + updatedAt: "desc", + }, + }); + + return checklists.map( + (checklist) => + new ChecklistEntity( + checklist.id, + checklist.userId, + checklist.systemId, + null, + null, + null, + checklist.createdAt, + checklist.updatedAt, + ), + ); + } + + async listChecklistsBySystemId( + req: ListChecklistsBySystemIdUseCaseRequest, + ): Promise { + const checklists = await this.prisma.checklists.findMany({ + where: { + systemId: req.systemId, + }, + orderBy: { + updatedAt: "desc", + }, + }); + + return checklists.map( + (checklist) => + new ChecklistEntity( + checklist.id, + checklist.userId, + checklist.systemId, + null, + null, + null, + checklist.createdAt, + checklist.updatedAt, + ), + ); + } + + async getItems(id: number): Promise { + const items = await this.prisma.checklistItems.findMany({ + where: { + checklistId: id, + }, + include: { + item: { + include: { + section: true, + }, + }, + }, + }); + + return items.map((item) => { + return new ChecklistItemEntity( + null, + new ItemEntity( + item.item.id, + item.item.code, + item.item.itemDesc, + item.item.recommendations, + item.item.isMandatory, + item.item.sectionId, + new SectionEntity(item.item.section.id, item.item.section.name), + null, + null, + ), + item.answer as AnswerType, + item.severityDegree as SeverityDegreeType, + item.userComment, + ); + }); + } + + async insertItems(id: number, items: ChecklistItemEntity[]): Promise { + await this.prisma.checklists.update({ + where: { id }, + data: { + ItemsChecklists: { + createMany: { + data: items.map((item) => { + return { + itemId: item.item.id, + answer: item.answer, + severityDegree: item.severityDegree, + userComment: item.userComment, + }; + }), + }, + }, + }, + }); + } + + async removeItems(id: number, itemsIds: number[]): Promise { + await this.prisma.checklistItems.deleteMany({ + where: { + AND: { + checklistId: id, + itemId: { + in: itemsIds, + }, + }, + }, + }); + } + + async updateItem(id: number, item: ChecklistItemEntity): Promise { + await this.prisma.checklistItems.update({ + where: { checklistId_itemId: { checklistId: id, itemId: item.item.id } }, + data: { + answer: item.answer, + severityDegree: item.severityDegree, + userComment: item.userComment, + }, + }); + } + + async getLaws(id: number): Promise { + const laws = await this.prisma.laws.findMany({ + where: { + checklists: { + some: { + id, + }, + }, + }, + }); + + return laws.map((law) => new LawEntity(law.id, law.name)); + } + + async insertLaws(id: number, lawsIds: number[]): Promise { + await this.prisma.checklists.update({ + where: { id }, + data: { + laws: { + connect: lawsIds.map((lawId) => ({ id: lawId })), + }, + }, + }); + } + + async removeLaws(id: number, lawsIds: number[]): Promise { + await this.prisma.checklists.update({ + where: { id }, + data: { + laws: { + disconnect: lawsIds.map((lawId) => ({ id: lawId })), + }, + }, + }); + } + + async getDevices(id: number): Promise { + const devices = await this.prisma.devices.findMany({ + where: { + checklists: { + some: { + id, + }, + }, + }, + }); + + return devices.map((device) => new DeviceEntity(device.id, device.name)); + } + + async insertDevices(id: number, devicesIds: number[]): Promise { + await this.prisma.checklists.update({ + where: { id }, + data: { + devices: { + connect: devicesIds.map((deviceId) => ({ id: deviceId })), + }, + }, + }); + } + + async removeDevices(id: number, devicesIds: number[]): Promise { + await this.prisma.checklists.update({ + where: { id }, + data: { + devices: { + disconnect: devicesIds.map((deviceId) => ({ id: deviceId })), + }, + }, + }); + } +} + +export { ChecklistPrismaRepository }; diff --git a/src/infrastructure/provider/repository/prisma/device.ts b/src/infrastructure/provider/repository/prisma/device.ts new file mode 100644 index 0000000..0e4e55d --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/device.ts @@ -0,0 +1,33 @@ +import { DeviceEntity } from "../../../../domain/entity/device"; +import { DeviceRepositoryInterface } from "../../../../domain/usecase/repository/device"; +import { CreateDeviceUseCaseRequest } from "../../../../domain/usecase/ucio/device"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; + +export class DevicePrismaRepository + extends PrismaRepository + implements DeviceRepositoryInterface +{ + protected withTransaction(tx: Prisma.TransactionClient): this { + return new DevicePrismaRepository(tx) as this; + } + + create(req: CreateDeviceUseCaseRequest): Promise { + throw new Error("Method not implemented." + req); + } + + async existByIds(ids: number[]): Promise { + const items = await this.prisma.devices.findMany({ + where: { + id: { in: ids }, + }, + }); + + return ids.filter((id) => !items.find((item) => item.id === id)); + } + + async list(): Promise { + const devices = await this.prisma.devices.findMany({}); + return devices.map((Device) => new DeviceEntity(Device.id, Device.name)); + } +} diff --git a/src/infrastructure/provider/repository/prisma/item.ts b/src/infrastructure/provider/repository/prisma/item.ts new file mode 100644 index 0000000..03a56b7 --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/item.ts @@ -0,0 +1,92 @@ +import { ItemRepositoryInterface } from "../../../../domain/usecase/repository/item"; +import { ItemEntity } from "../../../../domain/entity/item"; +import { + CreateItemUseCaseRequest, + ListItemsUseCaseRequest, +} from "../../../../domain/usecase/ucio/item"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; +import { SectionEntity } from "../../../../domain/entity/section"; +import { LawEntity } from "../../../../domain/entity/law"; +import { DeviceEntity } from "../../../../domain/entity/device"; + +class ItemPrismaRepository + extends PrismaRepository + implements ItemRepositoryInterface +{ + protected withTransaction(tx: Prisma.TransactionClient): this { + return new ItemPrismaRepository(tx) as this; + } + + createItem(req: CreateItemUseCaseRequest): Promise { + throw new Error("Method not implemented." + req); + } + + async itemsExistByIds(ids: number[]): Promise { + const items = await this.prisma.items.findMany({ + where: { + id: { in: ids }, + }, + }); + + return ids.filter((id) => !items.find((item) => item.id === id)); + } + + async list(req: ListItemsUseCaseRequest): Promise { + const items = await this.prisma.items.findMany({ + where: { + laws: { + some: { + id: { in: req.laws }, + }, + }, + ...(req.devices?.length + ? { + OR: [ + { + devices: { + some: { + id: { in: req.devices }, + }, + }, + }, + { + devices: { + none: {}, // também aceita quem não tem nenhum device + }, + }, + ], + } + : { + devices: { + none: {}, // se não passou nada, só traz quem não tem device + }, + }), + }, + include: { + laws: true, + devices: true, + section: true, + }, + }); + + return items.map( + (item) => + new ItemEntity( + item.id, + item.code, + item.itemDesc, + item.recommendations, + item.isMandatory, + item.sectionId, + new SectionEntity(item.section.id, item.section.name), + item.laws.map((law) => new LawEntity(law.id, law.name)), + item.devices.map( + (device) => new DeviceEntity(device.id, device.name), + ), + ), + ); + } +} + +export { ItemPrismaRepository }; diff --git a/src/infrastructure/provider/repository/prisma/law.ts b/src/infrastructure/provider/repository/prisma/law.ts new file mode 100644 index 0000000..6c5caca --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/law.ts @@ -0,0 +1,34 @@ +import { LawEntity } from "../../../../domain/entity/law"; +import { LawRepositoryInterface } from "../../../../domain/usecase/repository/law"; +import { CreateLawUseCaseRequest } from "../../../../domain/usecase/ucio/law"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; + +export class LawPrismaRepository + extends PrismaRepository + implements LawRepositoryInterface +{ + protected withTransaction(tx: Prisma.TransactionClient): this { + return new LawPrismaRepository(tx) as this; + } + + create(req: CreateLawUseCaseRequest): Promise { + throw new Error("Method not implemented." + req); + } + + async existByIds(ids: number[]): Promise { + const items = await this.prisma.laws.findMany({ + where: { + id: { in: ids }, + }, + }); + + return ids.filter((id) => !items.find((item) => item.id === id)); + } + + async list(): Promise { + const laws = await this.prisma.laws.findMany({}); + + return laws.map((law) => new LawEntity(law.id, law.name)); + } +} diff --git a/src/infrastructure/provider/repository/prisma/prismaRepositoryFactory.ts b/src/infrastructure/provider/repository/prisma/prismaRepositoryFactory.ts new file mode 100644 index 0000000..34ed22b --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/prismaRepositoryFactory.ts @@ -0,0 +1,54 @@ +import { PrismaClient } from "@prisma/client"; +import { AuthJWTRepository } from "../auth"; +import { ChecklistPrismaRepository } from "./checklist"; +import { ItemPrismaRepository } from "./item"; +import { SystemPrismaRepository } from "./system"; +import { UserPrismaRepository } from "./user"; +import { AuthRepositoryInterface } from "../../../../domain/usecase/repository/auth"; +import { ChecklistRepositoryInterface } from "../../../../domain/usecase/repository/checklist"; +import { ItemRepositoryInterface } from "../../../../domain/usecase/repository/item"; +import { SystemRepositoryInterface } from "../../../../domain/usecase/repository/system"; +import { UserRepositoryInterface } from "../../../../domain/usecase/repository/user"; +import { RepositoryFactory } from "../../../../domain/factory/repositoryFactory"; +import { LawPrismaRepository } from "./law"; +import { DevicePrismaRepository } from "./device"; +import { DeviceRepositoryInterface } from "../../../../domain/usecase/repository/device"; +import { LawRepositoryInterface } from "../../../../domain/usecase/repository/law"; +import { SectionRepositoryInterface } from "../../../../domain/usecase/repository/section"; +import { SectionPrismaRepository } from "./section"; + +export class PrismaRepositoryFactory implements RepositoryFactory { + constructor(private prisma: PrismaClient) {} + + makeUserRepository(): UserRepositoryInterface { + return new UserPrismaRepository(this.prisma); + } + + makeSystemRepository(): SystemRepositoryInterface { + return new SystemPrismaRepository(this.prisma); + } + + makeChecklistRepository(): ChecklistRepositoryInterface { + return new ChecklistPrismaRepository(this.prisma); + } + + makeItemRepository(): ItemRepositoryInterface { + return new ItemPrismaRepository(this.prisma); + } + + makeLawRepository(): LawRepositoryInterface { + return new LawPrismaRepository(this.prisma); + } + + makeDeviceRepository(): DeviceRepositoryInterface { + return new DevicePrismaRepository(this.prisma); + } + + makeSectionRepository(): SectionRepositoryInterface { + return new SectionPrismaRepository(this.prisma); + } + + makeAuthRepository(): AuthRepositoryInterface { + return new AuthJWTRepository(); + } +} diff --git a/src/infrastructure/provider/repository/prisma/repository.ts b/src/infrastructure/provider/repository/prisma/repository.ts new file mode 100644 index 0000000..9933d5b --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/repository.ts @@ -0,0 +1,16 @@ +import { Prisma, PrismaClient } from "@prisma/client"; + +export abstract class PrismaRepository { + constructor(protected prisma: PrismaClient | Prisma.TransactionClient) {} + + protected abstract withTransaction(tx: Prisma.TransactionClient): this; + + async runInTransaction(fn: (repo: this) => Promise): Promise { + return (this.prisma as PrismaClient).$transaction( + async (tx: Prisma.TransactionClient) => { + const txRepo = this.withTransaction(tx); + return fn(txRepo); + }, + ); + } +} diff --git a/src/infrastructure/provider/repository/prisma/section.ts b/src/infrastructure/provider/repository/prisma/section.ts new file mode 100644 index 0000000..815a538 --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/section.ts @@ -0,0 +1,21 @@ +import { SectionEntity } from "../../../../domain/entity/section"; +import { SectionRepositoryInterface } from "../../../../domain/usecase/repository/section"; +import { CreateSectionUseCaseRequest } from "../../../../domain/usecase/ucio/section"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; + +export class SectionPrismaRepository + extends PrismaRepository + implements SectionRepositoryInterface +{ + protected withTransaction(tx: Prisma.TransactionClient): this { + return new SectionPrismaRepository(tx) as this; + } + + async create(req: CreateSectionUseCaseRequest): Promise { + const section = await this.prisma.sections.create({ + data: { name: req.name }, + }); + return new SectionEntity(section.id, section.name); + } +} diff --git a/src/infrastructure/provider/repository/prisma/system.ts b/src/infrastructure/provider/repository/prisma/system.ts new file mode 100644 index 0000000..c525bd5 --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/system.ts @@ -0,0 +1,98 @@ +import { SystemEntity } from "@/domain/entity/system"; +import { + CreateSystemUseCaseRequest, + DeleteSystemUseCaseRequest, + ListSystemsByUserIdUseCaseRequest, + UpdateSystemUseCaseRequest, +} from "../../../../domain/usecase/ucio/system"; +import { SystemRepositoryInterface } from "../../../../domain/usecase/repository/system"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; + +class SystemPrismaRepository + extends PrismaRepository + implements SystemRepositoryInterface +{ + protected withTransaction(tx: Prisma.TransactionClient): this { + return new SystemPrismaRepository(tx) as this; + } + + async createSystem(req: CreateSystemUseCaseRequest): Promise { + const system = await this.prisma.systems.create({ + data: { + name: req.name, + description: req.description, + userId: req.userId, + }, + }); + + return new SystemEntity( + system.id, + system.name, + system.description, + system.userId, + ); + } + + async listSystemsByUserId( + req: ListSystemsByUserIdUseCaseRequest, + ): Promise { + const systems = await this.prisma.systems.findMany({ + where: { + userId: req.userId, + }, + orderBy: { + updatedAt: "desc", + }, + }); + + return systems.map( + (system) => + new SystemEntity( + system.id, + system.name, + system.description, + system.userId, + ), + ); + } + + async getSystem(id: number): Promise { + const system = await this.prisma.systems.findUnique({ + where: { + id, + }, + }); + + return system + ? new SystemEntity( + system.id, + system.name, + system.description, + system.userId, + ) + : null; + } + + async deleteSystem(req: DeleteSystemUseCaseRequest): Promise { + await this.prisma.systems.delete({ + where: { + id: req.id, + }, + }); + } + + async updateSystem(req: UpdateSystemUseCaseRequest): Promise { + await this.prisma.systems.update({ + where: { + id: req.id, + }, + data: { + name: req.name, + description: req.description, + }, + }); + } +} + +export { SystemPrismaRepository }; diff --git a/src/infrastructure/provider/repository/prisma/user.ts b/src/infrastructure/provider/repository/prisma/user.ts new file mode 100644 index 0000000..4c94143 --- /dev/null +++ b/src/infrastructure/provider/repository/prisma/user.ts @@ -0,0 +1,97 @@ +import * as ucio from "@/domain/usecase/ucio/user"; +import { UserEntity } from "../../../../domain/entity/user"; +import { UserRepositoryInterface } from "../../../../domain/usecase/repository/user"; +import bcrypt from "bcryptjs"; +import { PrismaRepository } from "./repository"; +import { Prisma } from "@prisma/client"; +const { compareSync } = bcrypt; + +class UserPrismaRepository + extends PrismaRepository + implements UserRepositoryInterface +{ + protected withTransaction(tx: Prisma.TransactionClient): this { + return new UserPrismaRepository(tx) as this; + } + + async checkUserByEmailExists(email: string, id?: number): Promise { + const user = await this.prisma.users.findFirst({ + where: { + email, + }, + }); + + return user && user.id !== id; + } + + async createUser(req: ucio.CreateUserUseCaseRequest): Promise { + const user = await this.prisma.users.create({ + data: { + name: req.name, + office: req.office, + email: req.email, + password: req.password, + }, + }); + + return user + ? new UserEntity(user.id, user.name, user.office, user.email, null) + : null; + } + + async login(req: ucio.LoginUseCaseRequest): Promise { + const user = await this.prisma.users.findFirst({ + where: { + email: req.email, + }, + }); + + if (user && compareSync(req.password, user.password)) { + delete user.password; + + return user + ? new UserEntity(user.id, user.name, user.office, user.email, null) + : null; + } + + return null; + } + + async getUser(id: number): Promise { + const user = await this.prisma.users.findFirst({ + where: { + id, + }, + }); + + return user + ? new UserEntity(user.id, user.name, user.office, user.email, null) + : null; + } + + async updateUser(req: ucio.UpdateUserUseCaseRequest) { + const user = await this.prisma.users.update({ + where: { + id: req.id, + }, + data: { + name: req.name, + office: req.office, + }, + }); + + return user + ? new UserEntity(user.id, user.name, user.office, user.email, null) + : null; + } + + async deleteUser(req: ucio.DeleteUserUseCaseRequest) { + await this.prisma.users.delete({ + where: { + id: req.id, + }, + }); + } +} + +export { UserPrismaRepository }; diff --git a/src/infrastructure/provider/repository/system.ts b/src/infrastructure/provider/repository/system.ts deleted file mode 100644 index ece52d5..0000000 --- a/src/infrastructure/provider/repository/system.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as systemService from "@/internal/database/postgresql/system"; -import { SystemEntity } from "@/domain/entity/system"; -import { - CreateSystemUseCaseRequest, - DeleteSystemUseCaseRequest, - ListSystemsByUserIdUseCaseRequest, - UpdateSystemUseCaseRequest, -} from "../../../domain/usecase/ucio/system"; -import { SystemRepositoryInterface } from "../../../domain/usecase/repository/system"; - -class SystemPrismaRepository implements SystemRepositoryInterface { - async createSystem(req: CreateSystemUseCaseRequest): Promise { - return await systemService.createSystem(req); - } - - async listSystemsByUserId( - req: ListSystemsByUserIdUseCaseRequest, - ): Promise { - return await systemService.listSystemsByUserId(req.userId); - } - - async getSystem(id: number): Promise { - return await systemService.getSystem(id); - } - - async deleteSystem(req: DeleteSystemUseCaseRequest): Promise { - await systemService.deleteSystem(req.id); - } - - async updateSystem(req: UpdateSystemUseCaseRequest): Promise { - return await systemService.updateSystem(req); - } -} - -export { SystemPrismaRepository }; diff --git a/src/infrastructure/provider/repository/user.ts b/src/infrastructure/provider/repository/user.ts deleted file mode 100644 index 52377ac..0000000 --- a/src/infrastructure/provider/repository/user.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as userService from "@/internal/database/postgresql/user"; -import * as userUcio from "@/domain/usecase/ucio/user"; -import { UserEntity } from "../../../domain/entity/user"; -import { UserRepositoryInterface } from "../../../domain/usecase/repository/user"; - -class UserPrismaRepository implements UserRepositoryInterface { - async checkUserByEmailExists(email: string, id?: number): Promise { - return await userService.checkUserByEmailExists(email, id); - } - - async createUser( - user: userUcio.CreateUserUseCaseRequest, - ): Promise { - return await userService.createUser(user); - } - - async login(req: userUcio.LoginUseCaseRequest): Promise { - return await userService.login(req); - } - - getUser(id: number): Promise { - return userService.getUser(id); - } - - async updateUser(req: userUcio.UpdateUserUseCaseRequest) { - return userService.updateUser(req); - } - - deleteUser(req: userUcio.DeleteUserUseCaseRequest) { - return userService.deleteUser(req.id); - } -} - -export { UserPrismaRepository }; diff --git a/src/main.ts b/src/main.ts index 5354ec8..7f025b7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,15 @@ import "dotenv/config"; import "module-alias/register"; import { CmdRest } from "./delivery/api/rest/cmd/server"; +import { PrismaRepositoryFactory } from "./infrastructure/provider/repository/prisma/prismaRepositoryFactory"; +import { RepositoryFactory } from "./domain/factory/repositoryFactory"; +import { PrismaClient } from "@prisma/client"; class Main { public restApp: CmdRest; - constructor() { - this.restApp = new CmdRest(); + constructor(factory: RepositoryFactory) { + this.restApp = new CmdRest(factory); } public init(): void { @@ -14,7 +17,8 @@ class Main { } } -const main = new Main(); +const prisma = new PrismaClient(); +const main = new Main(new PrismaRepositoryFactory(prisma)); main.init(); diff --git a/test/architecture/arch.spec.ts b/test/architecture/arch.spec.ts new file mode 100644 index 0000000..066edf4 --- /dev/null +++ b/test/architecture/arch.spec.ts @@ -0,0 +1,128 @@ +import { describe, it } from "vitest"; +import fg from "fast-glob"; +import fs from "fs"; + +const readImports = (file: string): string[] => { + const content = fs.readFileSync(file, "utf8"); + const importRegex = /from\s+['"](.+)['"]/g; + const imports: string[] = []; + let match; + + while ((match = importRegex.exec(content))) { + imports.push(match[1]); + } + + return imports; +}; + +const mapFolderName = (folders: string[]): string[] => + folders + .map((folder) => [`../${folder}`, `src/${folder}`, `@/${folder}`]) // vira um array com arrays + .flat(); // transforma em um único array com strings + +const hasForbiddenImport = ( + imp: string, + forbiddenSegments: string[], +): boolean => forbiddenSegments.some((segment) => imp.includes(segment)); // Check if the import path contains the forbidden segment + +describe("Architecture Rules", () => { + it("delivery must NOT import from infrastructure", async () => { + const files = await fg("**/delivery/**/*.ts", { absolute: true }); + + const folder = "infrastructure"; + const forbidden = mapFolderName([folder]); + + const violations: string[] = []; + + for (const file of files) { + const imports = readImports(file); + for (const imp of imports) { + if (hasForbiddenImport(imp, forbidden)) { + violations.push(`${file} imports ${imp}`); + } + } + } + + if (violations.length > 0) { + throw new Error( + `❌ Found ${violations.length} delivery->infrastructure violations:\n` + + violations.join("\n"), + ); + } + }); + + it("infrastructure must NOT import from delivery", async () => { + const files = await fg("**/infrastructure/**/*.ts", { absolute: true }); + + const folder = "delivery"; + const forbidden = mapFolderName([folder]); + + const violations: string[] = []; + + for (const file of files) { + const imports = readImports(file); + for (const imp of imports) { + if (hasForbiddenImport(imp, forbidden)) { + violations.push(`${file} imports ${imp}`); + } + } + } + + if (violations.length > 0) { + throw new Error( + `❌ Found ${violations.length} infrastructure->delivery violations:\n` + + violations.join("\n"), + ); + } + }); + + it("domain must NOT import from delivery or infrastructure", async () => { + const files = await fg("**/domain/**/*.ts", { absolute: true }); + + const violations: string[] = []; + + const folders = ["delivery", "infrastructure"]; + const forbidden = mapFolderName(folders); + + for (const file of files) { + const imports = readImports(file); + for (const imp of imports) { + if (hasForbiddenImport(imp, forbidden)) { + violations.push(`${file} imports ${imp}`); + } + } + } + + if (violations.length > 0) { + throw new Error( + `❌ Found ${violations.length} domain->(delivery/infrastructure) violations:\n` + + violations.join("\n"), + ); + } + }); + + it("src should NOT import from test folder unless it is a test file", async () => { + const files = await fg("src/**/*.ts", { absolute: true }); + + const violations: string[] = []; + + for (const file of files) { + const isTestFile = file.endsWith(".spec.ts") || file.endsWith(".test.ts"); + const imports = readImports(file); + + for (const imp of imports) { + const isTestImport = imp.includes("../test"); + if (isTestImport && !isTestFile) { + violations.push(`${file} imports ${imp}`); + } + } + } + + if (violations.length > 0) { + throw new Error( + `❌ Found ${violations.length} src->test violations:\n` + + violations.join("\n"), + ); + } + }); +}); diff --git a/test/factory.ts b/test/factory.ts new file mode 100644 index 0000000..a599804 --- /dev/null +++ b/test/factory.ts @@ -0,0 +1,4 @@ +import { InMemoryRepositoryFactory } from "./repository/InMemoryRepositoryFactory"; +import { RepositoryFactory } from "../src/domain/factory/repositoryFactory"; + +export const testFactory: RepositoryFactory = new InMemoryRepositoryFactory(); diff --git a/test/repository/InMemoryRepositoryFactory.ts b/test/repository/InMemoryRepositoryFactory.ts new file mode 100644 index 0000000..a418a76 --- /dev/null +++ b/test/repository/InMemoryRepositoryFactory.ts @@ -0,0 +1,51 @@ +import { AuthFakeRepository } from "./auth"; +import { ChecklistInMemoryRepository } from "./checklist"; +import { ItemInMemoryRepository } from "./item"; +import { SystemInMemoryRepository } from "./system"; +import { UserInMemoryRepository } from "./user"; +import { LawInMemoryRepository } from "./law"; +import { DeviceInMemoryRepository } from "./device"; +import { AuthRepositoryInterface } from "../../src/domain/usecase/repository/auth"; +import { ChecklistRepositoryInterface } from "../../src/domain/usecase/repository/checklist"; +import { DeviceRepositoryInterface } from "../../src/domain/usecase/repository/device"; +import { ItemRepositoryInterface } from "../../src/domain/usecase/repository/item"; +import { LawRepositoryInterface } from "../../src/domain/usecase/repository/law"; +import { SystemRepositoryInterface } from "../../src/domain/usecase/repository/system"; +import { UserRepositoryInterface } from "../../src/domain/usecase/repository/user"; +import { SectionRepositoryInterface } from "../../src/domain/usecase/repository/section"; +import { RepositoryFactory } from "../../src/domain/factory/repositoryFactory"; +import { SectionInMemoryRepository } from "./section"; + +export class InMemoryRepositoryFactory implements RepositoryFactory { + makeUserRepository(): UserRepositoryInterface { + return new UserInMemoryRepository(); + } + + makeSystemRepository(): SystemRepositoryInterface { + return new SystemInMemoryRepository(); + } + + makeChecklistRepository(): ChecklistRepositoryInterface { + return new ChecklistInMemoryRepository(); + } + + makeItemRepository(): ItemRepositoryInterface { + return new ItemInMemoryRepository(); + } + + makeLawRepository(): LawRepositoryInterface { + return new LawInMemoryRepository(); + } + + makeDeviceRepository(): DeviceRepositoryInterface { + return new DeviceInMemoryRepository(); + } + + makeSectionRepository(): SectionRepositoryInterface { + return new SectionInMemoryRepository(); + } + + makeAuthRepository(): AuthRepositoryInterface { + return new AuthFakeRepository(); + } +} diff --git a/test/repository/checklist.ts b/test/repository/checklist.ts index 5049d33..bc3bb30 100644 --- a/test/repository/checklist.ts +++ b/test/repository/checklist.ts @@ -1,12 +1,16 @@ import { ChecklistEntity } from "../../src/domain/entity/checklist"; import { ChecklistRepositoryInterface } from "../../src/domain/usecase/repository/checklist"; +import { ChecklistItemEntity } from "../../src/domain/entity/checklistItem"; +import { ItemEntity } from "../../src/domain/entity/item"; import { CreateChecklistUseCaseRequest, DeleteChecklistUseCaseRequest, UpdateChecklistUseCaseRequest, ListChecklistsByUserIdUseCaseRequest, ListChecklistsBySystemIdUseCaseRequest, -} from "../domain/usecase/ucio/checklist"; +} from "../../src/domain/usecase/ucio/checklist"; +import { LawEntity } from "../../src/domain/entity/law"; +import { DeviceEntity } from "../../src/domain/entity/device"; class ChecklistInMemoryRepository implements ChecklistRepositoryInterface { public items: ChecklistEntity[] = []; @@ -19,9 +23,18 @@ class ChecklistInMemoryRepository implements ChecklistRepositoryInterface { this.counter + 1, checklist.userId, checklist.systemId, - checklist.checklistData, - checklist.isGeneral, - checklist.isIot, + checklist.items.map( + (item) => + new ChecklistItemEntity( + null, + new ItemEntity(item.id, null, null, null, null, null, null), + item.answer, + item.severityDegree, + item.userComment, + ), + ), + checklist.laws.map((id) => new LawEntity(id, null)), + checklist.devices.map((id) => new DeviceEntity(id, null)), new Date(), new Date(), ); @@ -56,9 +69,6 @@ class ChecklistInMemoryRepository implements ChecklistRepositoryInterface { } this.items[index].systemId = req.systemId; - this.items[index].checklistData = req.checklistData; - this.items[index].isGeneral = req.isGeneral; - this.items[index].isIot = req.isIot; this.items[index].updatedAt = new Date(); } @@ -73,6 +83,102 @@ class ChecklistInMemoryRepository implements ChecklistRepositoryInterface { ): Promise { return this.items.filter((item) => item.systemId === req.systemId); } + + async getItems(id: number): Promise { + const index = this.items.findIndex((item) => item.id === id); + + if (index === -1) { + return null; + } + + return this.items[index].checklistItems; + } + + async insertItems(id: number, items: ChecklistItemEntity[]): Promise { + const checklist = await this.getChecklist(id); + if (!checklist) return; + + checklist.checklistItems = [...(checklist.checklistItems || []), ...items]; + checklist.updatedAt = new Date(); + } + + async removeItems(id: number, itemsIds: number[]): Promise { + const checklist = await this.getChecklist(id); + if (!checklist) return; + + checklist.checklistItems = checklist.checklistItems.filter( + (item) => !itemsIds.includes(item.item.id), + ); + checklist.updatedAt = new Date(); + } + + async updateItem(id: number, item: ChecklistItemEntity): Promise { + const checklist = await this.getChecklist(id); + if (!checklist) return; + + const index = checklist.checklistItems.findIndex( + (ci) => ci.item.id === item.item.id, + ); + if (index === -1) return; + + checklist.checklistItems[index] = item; + checklist.updatedAt = new Date(); + } + + async getLaws(id: number): Promise { + const checklist = await this.getChecklist(id); + return checklist ? checklist.laws : []; + } + + async insertLaws(id: number, lawsIds: number[]): Promise { + const checklist = await this.getChecklist(id); + + if (!checklist) return; + + const newLaws = lawsIds.map((lawId) => new LawEntity(lawId, null)); + + checklist.laws = [...(checklist.laws || []), ...newLaws]; + + checklist.updatedAt = new Date(); + } + + async removeLaws(id: number, lawsIds: number[]): Promise { + const checklist = await this.getChecklist(id); + if (!checklist) return; + + checklist.laws = checklist.laws.filter((law) => !lawsIds.includes(law.id)); + checklist.updatedAt = new Date(); + } + + async getDevices(id: number): Promise { + const checklist = await this.getChecklist(id); + return checklist ? checklist.devices : []; + } + + async insertDevices(id: number, devicesIds: number[]): Promise { + const checklist = await this.getChecklist(id); + if (!checklist) return; + + const newDevices = devicesIds.map( + (deviceId) => new DeviceEntity(deviceId, null), + ); + checklist.devices = [...(checklist.devices || []), ...newDevices]; + checklist.updatedAt = new Date(); + } + + async removeDevices(id: number, devicesIds: number[]): Promise { + const checklist = await this.getChecklist(id); + if (!checklist) return; + + checklist.devices = checklist.devices.filter( + (device) => !devicesIds.includes(device.id), + ); + checklist.updatedAt = new Date(); + } + + async runInTransaction(fn: (repo: this) => Promise): Promise { + return fn(this); + } } export { ChecklistInMemoryRepository }; diff --git a/test/repository/device.ts b/test/repository/device.ts new file mode 100644 index 0000000..b0b9b82 --- /dev/null +++ b/test/repository/device.ts @@ -0,0 +1,27 @@ +import { DeviceEntity } from "../../src/domain/entity/device"; +import { DeviceRepositoryInterface } from "../../src/domain/usecase/repository/device"; +import { CreateDeviceUseCaseRequest } from "../../src/domain/usecase/ucio/device"; + +// eslint-disable-next-line prettier/prettier +export class DeviceInMemoryRepository implements DeviceRepositoryInterface { + public items: DeviceEntity[] = []; + private counter = 0; + + async create(req: CreateDeviceUseCaseRequest): Promise { + this.counter += 1; + + const item = new DeviceEntity(this.counter, req.name); + + this.items.push(item); + + return item; + } + + async existByIds(ids: number[]): Promise { + return ids.filter((id) => !this.items.find((item) => item.id === id)); + } + + async list(): Promise { + return this.items; + } +} diff --git a/test/repository/item.ts b/test/repository/item.ts new file mode 100644 index 0000000..295a664 --- /dev/null +++ b/test/repository/item.ts @@ -0,0 +1,57 @@ +import { ItemEntity } from "../../src/domain/entity/item"; +import { ItemRepositoryInterface } from "../../src/domain/usecase/repository/item"; +import { + CreateItemUseCaseRequest, + ListItemsUseCaseRequest, +} from "../../src/domain/usecase/ucio/item"; +import { LawEntity } from "../../src/domain/entity/law"; +import { DeviceEntity } from "../../src/domain/entity/device"; +import { SectionEntity } from "../../src/domain/entity/section"; + +export class ItemInMemoryRepository implements ItemRepositoryInterface { + public items: ItemEntity[] = []; + private counter = 0; + + async createItem(req: CreateItemUseCaseRequest): Promise { + this.counter += 1; + + const item = new ItemEntity( + this.counter, + req.code, + req.itemDesc, + req.recommendations, + req.isMandatory, + req.sectionId, + new SectionEntity(req.sectionId, null), + req.lawsIds.map((id) => new LawEntity(id, null)), + req.devicesIds.map((id) => new DeviceEntity(id, null)), + ); + + this.items.push(item); + + return item; + } + + async itemsExistByIds(ids: number[]): Promise { + return ids.filter((id) => !this.items.find((item) => item.id === id)); + } + + async list(req: ListItemsUseCaseRequest): Promise { + return this.items.filter((item) => { + // Deve ter alguma law com ID presente em req.laws + const hasMatchingLaw = + req.laws?.length > 0 + ? item.laws.some((law) => req.laws.includes(law.id)) + : true; + + // Lógica de devices: + const hasMatchingDevice = + req.devices?.length > 0 + ? item.devices.length === 0 || + item.devices.some((device) => req.devices.includes(device.id)) + : item.devices.length === 0; + + return hasMatchingLaw && hasMatchingDevice; + }); + } +} diff --git a/test/repository/law.ts b/test/repository/law.ts new file mode 100644 index 0000000..2386fda --- /dev/null +++ b/test/repository/law.ts @@ -0,0 +1,26 @@ +import { LawEntity } from "../../src/domain/entity/law"; +import { LawRepositoryInterface } from "../../src/domain/usecase/repository/law"; +import { CreateLawUseCaseRequest } from "../../src/domain/usecase/ucio/law"; + +export class LawInMemoryRepository implements LawRepositoryInterface { + public items: LawEntity[] = []; + private counter = 0; + + async create(req: CreateLawUseCaseRequest): Promise { + this.counter += 1; + + const item = new LawEntity(this.counter, req.name); + + this.items.push(item); + + return item; + } + + async existByIds(ids: number[]): Promise { + return ids.filter((id) => !this.items.find((item) => item.id === id)); + } + + async list(): Promise { + return this.items; + } +} diff --git a/test/repository/section.ts b/test/repository/section.ts new file mode 100644 index 0000000..2a3f341 --- /dev/null +++ b/test/repository/section.ts @@ -0,0 +1,15 @@ +import { SectionEntity } from "../../src/domain/entity/section"; +import { SectionRepositoryInterface } from "../../src/domain/usecase/repository/section"; +import { CreateSectionUseCaseRequest } from "../domain/usecase/ucio/section"; + +export class SectionInMemoryRepository implements SectionRepositoryInterface { + public items: SectionEntity[] = []; + private counter = 0; + + async create(req: CreateSectionUseCaseRequest): Promise { + this.counter += 1; + const section = new SectionEntity(this.counter, req.name); + this.items.push(section); + return section; + } +} diff --git a/test/utils/createChecklist.ts b/test/utils/createChecklist.ts new file mode 100644 index 0000000..d7e79cf --- /dev/null +++ b/test/utils/createChecklist.ts @@ -0,0 +1,38 @@ +import request from "supertest"; +import express from "express"; +import { createUserAndSystem } from "./createUserAndSystem"; +import { ChecklistEntity } from "../../src/domain/entity/checklist"; + +export const createChecklist = async (app: express.Application) => { + const { user, token, system } = await createUserAndSystem(app); + + const response = await request(app) + .post("/checklists") + .set("Authorization", `Bearer ${token}`) + .send({ + userId: user.id, + systemId: system.id, + isIot: false, + items: [ + { + id: 1, + answer: "Sim", + severityDegree: undefined, + userComment: undefined, + }, + ], + laws: [1], + devices: [], + }); + + const { checklist } = response.body as { + checklist: ChecklistEntity; + }; + + return { + user, + token, + system, + checklist, + }; +}; diff --git a/test/utils/createDevice.ts b/test/utils/createDevice.ts new file mode 100644 index 0000000..5826b3b --- /dev/null +++ b/test/utils/createDevice.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from "@prisma/client"; +import { CreateDeviceUseCaseRequest } from "../domain/usecase/ucio/device"; + +export const createDevice = async ( + prisma: PrismaClient, + req: CreateDeviceUseCaseRequest = { + name: "Sensor IoT", + }, +) => { + return await prisma.devices.create({ + data: { + name: req.name, + }, + }); +}; diff --git a/test/utils/createItem.ts b/test/utils/createItem.ts new file mode 100644 index 0000000..f0af3e0 --- /dev/null +++ b/test/utils/createItem.ts @@ -0,0 +1,31 @@ +import { PrismaClient } from "@prisma/client"; +import { CreateItemUseCaseRequest } from "../../src/domain/usecase/ucio/item"; + +export const createItem = async ( + prisma: PrismaClient, + req: CreateItemUseCaseRequest = { + code: "I-01", + itemDesc: "itemDesc", + recommendations: "recommendations", + isMandatory: true, + sectionId: 1, + lawsIds: [1], + devicesIds: [1], + }, +) => { + return await prisma.items.create({ + data: { + code: req.code, + itemDesc: req.itemDesc, + recommendations: req.recommendations, + isMandatory: req.isMandatory, + sectionId: req.sectionId, + laws: { + connect: req.lawsIds.map((id) => ({ id })), + }, + devices: { + connect: req.devicesIds.map((id) => ({ id })), + }, + }, + }); +}; diff --git a/test/utils/createLaw.ts b/test/utils/createLaw.ts new file mode 100644 index 0000000..4d474cc --- /dev/null +++ b/test/utils/createLaw.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from "@prisma/client"; +import { CreateLawUseCaseRequest } from "../../src/domain/usecase/ucio/law"; + +export const createLaw = async ( + prisma: PrismaClient, + req: CreateLawUseCaseRequest = { + name: "LGPD", + }, +) => { + return await prisma.laws.create({ + data: { + name: req.name, + }, + }); +}; diff --git a/test/utils/createSection.ts b/test/utils/createSection.ts new file mode 100644 index 0000000..1f87345 --- /dev/null +++ b/test/utils/createSection.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from "@prisma/client"; +import { CreateSectionUseCaseRequest } from "../../src/domain/usecase/ucio/section"; + +export const createSection = async ( + prisma: PrismaClient, + req: CreateSectionUseCaseRequest = { + name: "Sobre transparência de Dados", + }, +) => { + return await prisma.sections.create({ + data: { + name: req.name, + }, + }); +}; diff --git a/test/utils/mockGenerator.ts b/test/utils/mockGenerator.ts index ba076aa..a03306e 100644 --- a/test/utils/mockGenerator.ts +++ b/test/utils/mockGenerator.ts @@ -2,13 +2,24 @@ import bcrypt from "bcryptjs"; import { SystemRepositoryInterface } from "../../src/domain/usecase/repository/system"; import { UserRepositoryInterface } from "../../src/domain/usecase/repository/user"; import { ChecklistRepositoryInterface } from "../../src/domain/usecase/repository/checklist"; -import { Json } from "../../src/domain/@types"; +import { + AnswerType, + SeverityDegreeType, +} from "../../src/domain/entity/checklistItem"; +import { ItemRepositoryInterface } from "../../src/domain/usecase/repository/item"; +import { LawRepositoryInterface } from "../../src/domain/usecase/repository/law"; +import { DeviceRepositoryInterface } from "../../src/domain/usecase/repository/device"; +import { SectionRepositoryInterface } from "../../src/domain/usecase/repository/section"; const { genSaltSync, hashSync } = bcrypt; enum Repositories { "User" = "User", "System" = "System", "Checklist" = "Checklist", + "Item" = "Item", + "Law" = "Law", + "Device" = "Device", + "Section" = "Section", } class NoRepositoryError extends Error { @@ -21,18 +32,27 @@ class MockGenerator { private userRepository: UserRepositoryInterface; private systemRepository: SystemRepositoryInterface; private checklistRepository: ChecklistRepositoryInterface; - public checklistData: Json = { - message: "hello", - }; + private itemRepository: ItemRepositoryInterface; + private lawRepository: LawRepositoryInterface; + private deviceRepository: DeviceRepositoryInterface; + private sectionRepository: SectionRepositoryInterface; constructor( userRepository?: UserRepositoryInterface, systemRepository?: SystemRepositoryInterface, checklistRepository?: ChecklistRepositoryInterface, + itemRepository?: ItemRepositoryInterface, + lawRepository?: LawRepositoryInterface, + deviceRepository?: DeviceRepositoryInterface, + sectionRepository?: SectionRepositoryInterface, ) { this.userRepository = userRepository; this.systemRepository = systemRepository; this.checklistRepository = checklistRepository; + this.itemRepository = itemRepository; + this.lawRepository = lawRepository; + this.deviceRepository = deviceRepository; + this.sectionRepository = sectionRepository; } async createUserMock({ @@ -73,23 +93,80 @@ class MockGenerator { userId = 1, tokenUserId = 1, systemId = 1, - isGeneral = true, - isIot = false, - checklistData = this.checklistData, + items = [ + { + id: 1, + answer: "Sim" as AnswerType, + severityDegree: undefined as SeverityDegreeType, + userComment: undefined as string, + }, + ], + laws = [1], + devices = [1], } = {}) { if (this.checklistRepository) { return await this.checklistRepository.createChecklist({ userId, tokenUserId, systemId, - checklistData, - isGeneral, - isIot, + items, + laws, + devices, }); } throw new NoRepositoryError(Repositories.Checklist); } + async createItemMock({ + code = "I-01", + itemDesc = "itemDesc", + recommendations = "recommendations", + isMandatory = true, + sectionId = 1, + lawsIds = [1], + devicesIds = [1], + } = {}) { + if (this.itemRepository) { + return await this.itemRepository.createItem({ + code, + itemDesc, + recommendations, + isMandatory, + sectionId, + lawsIds, + devicesIds, + }); + } + throw new NoRepositoryError(Repositories.Item); + } + + async createLawMock({ name = "LGPD" } = {}) { + if (this.lawRepository) { + return await this.lawRepository.create({ + name, + }); + } + throw new NoRepositoryError(Repositories.Law); + } + + async createDeviceMock({ name = "Sensor IoT" } = {}) { + if (this.deviceRepository) { + return await this.deviceRepository.create({ + name, + }); + } + throw new NoRepositoryError(Repositories.Device); + } + + async createSectionMock({ name = "Sobre transparência de Dados" } = {}) { + if (this.sectionRepository) { + return await this.sectionRepository.create({ + name, + }); + } + throw new NoRepositoryError(Repositories.Section); + } + async createUserAndSystemMock() { const user = await this.createUserMock(); const system = await this.createSystemMock(); @@ -111,6 +188,30 @@ class MockGenerator { checklist, }; } + + async createItemAndChecklistMock() { + const item = await this.createItemMock(); + const checklist = await this.createChecklistMock(); + + return { + item, + checklist, + }; + } + + async createItemAndLawAndDeviceAndChecklistMock() { + const item = await this.createItemMock(); + const law = await this.createLawMock(); + const device = await this.createDeviceMock(); + const checklist = await this.createChecklistMock(); + + return { + item, + law, + device, + checklist, + }; + } } export { MockGenerator, NoRepositoryError };