Repository: henrylle/bia Branch: main Commit: 71b456307a63 Files: 86 Total size: 131.1 KB Directory structure: gitextract_71ikwe_m/ ├── .dockerignore ├── .gitignore ├── .kiro/ │ ├── agents/ │ │ └── bia.json │ ├── mcp-db.json │ ├── mcp-ecs.json │ └── rules/ │ ├── dockerfile.md │ ├── infraestrutura.md │ └── pipeline.md ├── .sequelizerc ├── .vscode/ │ └── launch.json ├── AmazonQ.md ├── Dockerfile ├── Dockerfile_checkdisponibilidade ├── README.md ├── api/ │ ├── controllers/ │ │ ├── tarefas.js │ │ └── versao.js │ ├── data/ │ │ └── tarefas.json │ ├── models/ │ │ ├── index.js │ │ └── tarefas.js │ └── routes/ │ ├── ping.js │ ├── tarefas.js │ └── versao.js ├── buildspec.yml ├── client/ │ ├── db.json │ ├── index.html │ ├── package.json │ ├── public/ │ │ ├── manifest.json │ │ └── robots.txt │ ├── src/ │ │ ├── App.jsx │ │ ├── components/ │ │ │ ├── About.js │ │ │ ├── About.jsx │ │ │ ├── AddTask.jsx │ │ │ ├── Button.jsx │ │ │ ├── DadosHenrylle.jsx │ │ │ ├── DebugLogs.jsx │ │ │ ├── Footer.jsx │ │ │ ├── Header.jsx │ │ │ ├── Modal.jsx │ │ │ ├── Task.jsx │ │ │ ├── Tasks.jsx │ │ │ └── VersionInfo.jsx │ │ ├── contexts/ │ │ │ ├── LogContext.jsx │ │ │ └── ThemeContext.jsx │ │ ├── index.css │ │ ├── main.jsx │ │ └── reportWebVitals.js │ └── vite.config.js ├── compose.yml ├── config/ │ ├── database.js │ ├── default.json │ └── express.js ├── database/ │ └── migrations/ │ └── 20210924000838-criar-tarefas.js ├── docs/ │ ├── README.md │ └── architecture/ │ └── aws-ecs-diagram.html ├── generate-sts-token.bat ├── generate-sts-token.sh ├── index.html ├── index.js ├── lib/ │ └── boot.js ├── package.json ├── rodar_app_local_unix.sh ├── rodar_app_local_windows.bat ├── scripts/ │ ├── criar_role_ssm.sh │ ├── ec2_principal.json │ ├── ecs/ │ │ ├── unix/ │ │ │ ├── build.sh │ │ │ ├── check-disponibilidade.sh │ │ │ ├── deploy.sh │ │ │ └── testar-latencia.sh │ │ └── windows/ │ │ ├── build.bat │ │ ├── check-disponibilidade.bat │ │ └── deploy.bat │ ├── generate-sts-token.sh │ ├── lancar_ec2_zona_a.sh │ ├── ligar_bia_local.sh │ ├── parar_bia_local.sh │ ├── setup_bia_dev_ubuntu_ui.sh │ ├── setup_cloudshell_ssm.sh │ ├── setup_ui_ubuntu.sh │ ├── setup_vscode+chrome.sh │ ├── start-session-bash.sh │ ├── user_data_ec2_zona_a.sh │ └── validar_recursos_zona_a.sh ├── scripts_evento/ │ └── README.md ├── server.js └── tests/ └── unit/ └── controllers/ ├── tarefas.test.js └── versao.test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ node_modules npm-debug.log .git .gitignore README.md .env .nyc_output coverage .env.local .env.development.local .env.test.local .env.production.local # Client specific client/node_modules client/build client/dist client/.env # Docker Dockerfile docker-compose.yml # IDE .vscode .idea *.swp *.swo # OS .DS_Store Thumbs.db ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules #React node_modules /client/node_modules #React production project /client/build /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: .kiro/agents/bia.json ================================================ { "name": "bia", "description": "Agente especialista em DevOps e Cloud AWS do projeto BIA da Formação AWS.", "prompt": "Você é um DevOps Engineer especialista em AWS Cloud. Você é parte do time de desenvolvimento do projeto BIA da Formação AWS. Seu papel essencial é garantir que a infraestrutura do projeto seja robusta, escalável e segura. Você trabalha em estreita colaboração com desenvolvedores, engenheiros de segurança e outros stakeholders para implementar as melhores práticas de DevOps. Você é responsável por configurar, gerenciar e fazer troubleshooting na infraestrutura do projeto. Você está configurado dentro de uma EC2 e vai acessar os serviços utilizando a role da instância.", "tools": [ "*" ], "resources": [ "file://AmazonQ.md", "file://README.md", "file://.kiro/rules/**/*.md" ] } ================================================ FILE: .kiro/mcp-db.json ================================================ { "mcpServers": { "postgres": { "command": "docker", "args": [ "run", "-i", "--rm", "--network=bia_default", "-e", "DATABASE_URI", "crystaldba/postgres-mcp", "--access-mode=unrestricted" ], "env": { "DATABASE_URI": "postgresql://postgres:postgres@database:5432/bia" } } } } //Source: https://github.com/crystaldba/postgres-mcp ================================================ FILE: .kiro/mcp-ecs.json ================================================ { "mcpServers": { "awslabs.ecs-mcp-server": { "command": "uvx", "args": ["--from", "awslabs-ecs-mcp-server", "ecs-mcp-server"], "env": { "FASTMCP_LOG_LEVEL": "ERROR", "FASTMCP_LOG_FILE": "/path/to/ecs-mcp-server.log", "ALLOW_WRITE": "false", "ALLOW_SENSITIVE_DATA": "false" } } } } //Source: https://awslabs.github.io/mcp/servers/ecs-mcp-server/ ================================================ FILE: .kiro/rules/dockerfile.md ================================================ # Regras para Dockerfile - Projeto BIA ## Filosofia de Desenvolvimento - **Público-alvo:** Alunos em aprendizado - **Abordagem:** Simplicidade acima de complexidade - **Objetivo:** Facilitar compreensão de quem que está na etapa inicial da jornada ## Regras Obrigatórias para Dockerfiles ### 1. Análise Prévia Obrigatória - **SEMPRE** verificar o package.json do projeto raiz - **SEMPRE** verificar o package.json do client (se existir) - **IDENTIFICAR** versão do Node.js necessária - **IDENTIFICAR** todas as tecnologias envolvidas (React, Vite, etc.) - **VERIFICAR** scripts de build e dependências - **VERIFICAR** variáveis de ambiente necessárias (ex: VITE_API_URL) - **ANALISAR** o Dockerfile original para configurações específicas ### 2. Configurações Importantes (NÃO IGNORAR) - **Imagem base:** Sempre usar ECR (`public.ecr.aws/docker/library/node:XX-slim`) - **Upgrade do npm:** Busque incluir, para estarmos sempre mais atualizados - **WORKDIR:** Trabalhar com (`/usr/src/app`) - **Instalação do curl:** Incluir para health checks - **Flags do npm:** Manter `--loglevel=error`, `--legacy-peer-deps` se necessário ### 3. Single Stage Sempre - **NUNCA** usar multi-stage builds - **NUNCA** sugerir otimizações complexas - Manter uma única etapa de build ### 4. Simplicidade Máxima - **EVITAR** mudanças de permissões (chmod, chown) - **EVITAR** criação de usuários não-root - **EVITAR** otimizações avançadas de camadas - Usar comandos básicos e diretos ### 5. Processo de Validação - **SEMPRE** perguntar se é para testar o Dockerfile - **QUANDO TESTAR:** Executar o projeto e verificar a rota de health check - **ROTA DE HEALTH:** Confirmar que `/api/versao` está respondendo corretamente ### 6. Criação de Arquivos - **NUNCA** sobrescrever Dockerfile existente - **SEMPRE** avisar ao usuário onde o novo Dockerfile está sendo criado - **SUGERIR** nome alternativo se já existir (ex: Dockerfile.new, Dockerfile.backup) ## Comandos de Teste ```bash # Build da imagem docker build -t bia-app . # Execução do container docker run -p 3001:8080 bia-app # Teste do health check curl http://localhost:3000/api/versao ``` ## O que NÃO fazer - ❌ Multi-stage builds - ❌ Otimizações de segurança complexas - ❌ Mudanças de usuário/permissões - ❌ Configurações avançadas de networking ## O que SEMPRE fazer - ✅ Single stage - ✅ Comandos simples e claros - ✅ Perguntar sobre teste - ✅ Validar health check quando testando ================================================ FILE: .kiro/rules/infraestrutura.md ================================================ # Regras de Infraestrutura - Projeto BIA ## Arquitetura Base - **Plataforma:** ECS com cluster de instâncias EC2 - **Evolução:** Iniciar sem ALB → Evoluir para incluir ALB ## Tipos de Instância - **RDS (Database):** t3.micro - **EC2 (ECS Cluster):** t3.micro ## Filosofia de Simplicidade - **Público-alvo:** Alunos em aprendizado - **Abordagem:** Simplicidade acima de complexidade - **Objetivo:** Facilitar compreensão de quem está na etapa inicial da jornada ## Recursos Avançados - NÃO INCLUIR - **Secrets Manager:** É um estágio mais avançado do aprendizado - **Multi-AZ deployments:** Manter configuração simples - **Auto Scaling complexo:** Usar configurações básicas ## Padrão de Nomenclatura ### Prefixo Padrão - **Prefixo:** `bia` (nome do projeto) ### Nomenclatura de Recursos ECS - **Cluster com ALB:** `cluster-bia-alb` - **Cluster sem ALB:** `cluster-bia` - **Task Definition com ALB:** `task-def-bia-alb` (prefixo task-def e sufixo -alb) - **Task Definition sem ALB:** `task-def-bia` (prefixo task-def) - **Service:** `service-bia` (sem alb) - **Service:** `service-bia-alb` (com alb) ### Sufixos dos Security Groups #### Cenário 1: Sem ALB (Inicial) - **Database (RDS):** `bia-db` - **EC2 (ECS Cluster):** `bia-web` #### Cenário 2: Com ALB (Evolução) - **Database (RDS):** `bia-db` - **Application Load Balancer:** `bia-alb` - **EC2 (ECS Cluster):** `bia-ec2` ## Regras de Security Groups ### Padrão de Descrição das Inbound Rules - **Formato obrigatório:** "acesso vindo de (nome do security group)" - **Exemplo:** "acesso vindo de bia-dev" - **Aplicação:** APENAS para inbound rules ### Database (bia-db) **Inbound Rules:** - **Porta:** 5432 (PostgreSQL) - **Sources:** - `bia-dev` → Descrição: "acesso vindo de bia-dev" - `bia-ec2` (quando com ALB) → Descrição: "acesso vindo de bia-ec2" - `bia-web` (quando sem ALB) → Descrição: "acesso vindo de bia-web" ### EC2 com ALB (bia-ec2) **Inbound Rules:** - **Protocolo:** All TCP - **Source:** `bia-alb` → Descrição: "acesso vindo de bia-alb" - **Motivo:** Portas aleatórias do ECS Service ### Application Load Balancer (bia-alb) **Inbound Rules:** - **Porta:** 80/443 - **Source:** 0.0.0.0/0 → Descrição: "acesso público HTTP/HTTPS" ## Banco de Dados - **Aproveitamento:** Usar banco existente na infraestrutura - **Não criar:** Novos recursos RDS nos templates - **Security Group:** Manter `bia-db` preparado para conexão ### Observações - As regras seguem o princípio de menor privilégio - Security Groups referenciam outros Security Groups para maior flexibilidade - Configuração permite evolução da arquitetura sem grandes mudanças ================================================ FILE: .kiro/rules/pipeline.md ================================================ # Regras de Pipeline - Projeto BIA ## Definição de Pipeline Sempre que falarmos de **pipeline** para este projeto, estamos nos referindo à combinação de: - **AWS CodePipeline** (orquestração) - **AWS CodeBuild** (build e deploy) ## Arquitetura do Pipeline ### Componentes Principais 1. **Source Stage:** GitHub como repositório de código 2. **Build Stage:** CodeBuild para build da aplicação 3. **Deploy Stage:** Deploy automático para ECS ### Configuração Base - **Buildspec:** Já configurado no arquivo `buildspec.yml` na raiz do projeto - **ECR:** Registry para armazenar imagens Docker - **ECS:** Target de deploy da aplicação ## Fluxo do Pipeline ### 1. Source (GitHub) - Trigger automático em push para branch principal - Webhook configurado para detectar mudanças ### 2. Build (CodeBuild) - Executa comandos definidos no `buildspec.yml` - Build da imagem Docker - Push da imagem para ECR ### 3. Deploy (ECS) - Deploy automático da nova imagem - Rolling update do serviço ECS - Health check da aplicação ## Configurações Importantes ### Variáveis de Ambiente - Configuradas no CodeBuild project - Variáveis específicas por ambiente (dev/prod) ### Permissões IAM - Role do CodeBuild com permissões para: - ECR (push/pull de imagens) - ECS (deploy de serviços) ### Monitoramento - CloudWatch Logs para logs do build - Métricas de build e deploy ## Boas Práticas ### Performance - Cache de dependências do npm - Otimização de layers do Docker ### Confiabilidade - Health checks após deploy - Rollback automático em caso de falha ## Troubleshooting Comum ### Build Failures - Verificar logs no CloudWatch - Validar permissões IAM - Confirmar configuração do buildspec.yml ### Deploy Issues - Verificar health checks do ECS - Validar configuração do service - Confirmar conectividade com RDS ## Evolução do Pipeline ### Fase Inicial - Pipeline simples: Source → Build → Deploy - Deploy direto para ECS ### Fase Avançada - Múltiplos ambientes (dev/staging/prod) - Aprovações manuais para produção ================================================ FILE: .sequelizerc ================================================ const path = require('path'); module.exports = { 'config': path.resolve('config', 'database.js'), 'models-path': path.resolve('app', 'models'), 'seeders-path': path.resolve('database', 'seeders'), 'migrations-path': path.resolve('database', 'migrations'), }; ================================================ FILE: .vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": "Testar DB com secrets", "request": "launch", "skipFiles": [ "/**" ], "type": "node", "env": { "DB_SECRET_NAME": "", "DB_REGION": "us-east-1", "AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": "", "DEBUG_SECRET": "true" }, "program": "${workspaceFolder}/config/database.js" }, { "name": "Debug Server", "request": "launch", "skipFiles": [ "/**" ], "type": "node", "env": { "DB_SECRET_NAME": "", "DB_REGION": "us-east-1", "AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": "", "DEBUG_SECRET": "true" }, // "program": "${workspaceFolder}/server.js" "program": "${workspaceFolder}/server.js" } ] } ================================================ FILE: AmazonQ.md ================================================ # Projeto BIA - Contexto e Análise ## Visão Geral do Projeto **Nome:** BIA **Versão:** 4.2.0 **Período da Imersão AWS & IA:** 23/05 a 24/05/2026 (Online e ao Vivo das 9h30 às 17h30) **Repositório:** https://github.com/henrylle/bia ## Impressões Iniciais do Desenvolvedor O projeto da BIA é um projeto educacional criado pelo Henrylle Maia (@henryllemaia) para ser usado nos eventos que ele realiza e servir de base para o treinamento Formação AWS. É um projeto concebido no ano de 2021 e que vem evoluindo usando as melhores práticas dentro da AWS. O foco base dele é fornecer uma estrutura educacional em que o aluno possa evoluir gradualmente, desde problemas simples até situações mais complexas. --- ## Análise Técnica (Amazon Q) ### Arquitetura Identificada - **Frontend:** React 17.0.2 com Vite para build - **Backend:** Node.js com Express 4.17.1 - **Banco de Dados:** PostgreSQL 16.1 - **ORM:** Sequelize 6.6.5 - **Containerização:** Docker com Docker Compose ### Stack Tecnológica **Frontend:** - React com React Router DOM - React Icons para ícones - Vite como bundler (configurado no Dockerfile) **Backend:** - Express.js como framework web - Sequelize como ORM - Morgan para logging - CORS habilitado - Express Session para gerenciamento de sessões - EJS e HBS como template engines **Infraestrutura:** - Docker containerizado - AWS SDK integrado (Secrets Manager, STS) - PostgreSQL como banco principal - Suporte a variáveis de ambiente ### Estrutura do Projeto ``` /bia ├── api/ # APIs do backend ├── client/ # Aplicação React ├── config/ # Configurações ├── database/ # Migrations e seeds ├── scripts/ # Scripts auxiliares ├── tests/ # Testes unitários (Jest) ├── docs/ # Documentação ├── compose.yml # Docker Compose ├── Dockerfile # Container da aplicação ├── buildspec.yml # AWS CodeBuild └── package.json # Dependências Node.js ``` ### Recursos AWS Identificados - **ECR:** Registry para imagens Docker (configurado no buildspec.yml) - **CodeBuild:** Pipeline de CI/CD já configurado - **Secrets Manager:** Gerenciamento de credenciais - **STS:** Tokens temporários de acesso ### Pontos de Atenção 1. **Segurança:** Credenciais hardcoded no compose.yml (apenas para desenvolvimento) 2. **Escalabilidade:** Aplicação monolítica, mas bem estruturada 3. **Monitoramento:** Healthcheck comentado no Docker Compose 4. **Testes:** Estrutura de testes presente com Jest ### Rotas da API para Testes - **`/api/versao`:** Retorna versão da aplicação (não usa banco) - **`/api/tarefas`:** Retorna dados do banco PostgreSQL (ideal para testar conectividade com RDS) ================================================ FILE: Dockerfile ================================================ FROM public.ecr.aws/docker/library/node:22.22.1-slim RUN npm install -g npm@11 --loglevel=error # Instalando curl RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app # Copiar package.json raiz primeiro COPY package*.json ./ RUN npm install --loglevel=error # Copiar package.json do client e instalar dependências (incluindo devDependencies para build) COPY client/package*.json ./client/ RUN cd client && npm install --legacy-peer-deps --loglevel=error # Copiar todos os arquivos COPY . . # Build do front-end com Vite RUN cd client && VITE_API_URL=http://localhost:3001 npm run build # Limpeza das dependências de desenvolvimento do client para reduzir tamanho RUN cd client && npm prune --production && rm -rf node_modules/.cache EXPOSE 8080 CMD [ "npm", "start" ] ================================================ FILE: Dockerfile_checkdisponibilidade ================================================ FROM alpine # Define o fuso horário como São Paulo ENV TZ=America/Sao_Paulo # Endereço para teste ENV URL=https://www.google.com.br # Instala curl e configura timezone RUN apk add --no-cache curl tzdata \ && cp /usr/share/zoneinfo/$TZ /etc/localtime \ && echo "$TZ" > /etc/timezone # Script de monitoramento ENTRYPOINT echo "Checando Site: $URL"; \ while sleep 5; do \ curl -o /dev/null -s -w "Status: %{http_code} - Hora: $(date +%H:%M:%S)\n" $URL; \ done ================================================ FILE: README.md ================================================ ## Projeto base para o evento Imersão AWS & IA que irei realizar. ### Período do evento: 23/05 e 24/05/2026 (Online e ao Vivo das 9h30 às 17h30) [>> Página de Inscrição do evento](https://org.imersaoaws.com.br/github/readme) #### Para rodar as migrations no container #### ``` docker compose exec server bash -c 'npx sequelize db:migrate' ``` ================================================ FILE: api/controllers/tarefas.js ================================================ const initializeModels = require("../models"); module.exports = () => { const controller = {}; controller.create = async (req, res) => { try { const { Tarefas } = await initializeModels(); let tarefa = { titulo: req.body.titulo, dia_atividade: req.body.dia_atividade, importante: req.body.importante, }; const data = await Tarefas.create(tarefa); res.send(data); } catch (err) { res.status(500).send({ message: err.message || "Deu ruim.", }); } }; controller.find = async (req, res) => { try { const { Tarefas } = await initializeModels(); let uuid = req.params.uuid; const data = await Tarefas.findByPk(uuid); if (data) { res.send(data); } else { res.status(404).send({ message: "Tarefa não encontrada.", }); } } catch (err) { res.status(500).send({ message: err.message || "Deu ruim.", }); } }; controller.delete = async (req, res) => { try { const { Tarefas } = await initializeModels(); let { uuid } = req.params; const result = await Tarefas.destroy({ where: { uuid: uuid, }, }); if (result) { res.send({ message: "Tarefa deletada com sucesso." }); } else { res.status(404).send({ message: "Tarefa não encontrada.", }); } } catch (err) { res.status(500).send({ message: err.message || "Deu ruim.", }); } }; controller.update_priority = async (req, res) => { try { const { Tarefas } = await initializeModels(); let { uuid } = req.params; await Tarefas.update(req.body, { where: { uuid: uuid, }, }); const data = await Tarefas.findByPk(uuid); if (data) { res.send(data); } else { res.status(404).send({ message: "Tarefa não encontrada.", }); } } catch (err) { res.status(500).send({ message: err.message || "Deu ruim.", }); } }; controller.findAll = async (req, res) => { try { const { Tarefas } = await initializeModels(); const data = await Tarefas.findAll(); res.send(data); } catch (err) { res.status(500).send({ message: err.message || "Deu ruim.", }); } }; return controller; }; ================================================ FILE: api/controllers/versao.js ================================================ module.exports = () => { const controller = {}; controller.get = async (req, res) => { const responseString = `Bia ${process.env.VERSAO_API || "4.2.0"}`; res.send(responseString); }; return controller; }; ================================================ FILE: api/data/tarefas.json ================================================ { "tasks": [{ "id": 1, "title": "Doctr Appoiment", "day": "Feb 5th at 2:30pm", "reminder": true }, { "title": "Party 2", "day": "16 de abril", "reminder": true, "id": 4 }, { "title": "Party 3", "day": "18 de Abril", "reminder": true, "id": 5 } ] } ================================================ FILE: api/models/index.js ================================================ "use strict"; const fs = require("fs"); const path = require("path"); const Sequelize = require("sequelize"); const basename = path.basename(__filename); const getConfig = require("../../config/database.js"); const db = {}; const initializeModels = async () => { const resolvedConfig = await getConfig(); const sequelize = new Sequelize(resolvedConfig); try { const files = await fs.promises.readdir(__dirname); for (const file of files) { if (file !== basename && file.slice(-3) === ".js") { const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); db[model.name] = model; } } Object.keys(db).forEach(modelName => { if (db[modelName].associate) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize; return db; } catch (error) { console.error("Erro ao inicializar os modelos:", error); throw error; } }; module.exports = initializeModels; ================================================ FILE: api/models/tarefas.js ================================================ module.exports = (sequelize, DataTypes) => { const Tarefas = sequelize.define("Tarefas", { uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1, primaryKey: true, }, titulo: DataTypes.STRING, dia_atividade: DataTypes.STRING, importante: DataTypes.BOOLEAN, }); return Tarefas; }; ================================================ FILE: api/routes/ping.js ================================================ module.exports = (app) => { app.route("/api/ping").get((req, res) => { res.json("Rota funcionando. Pong!"); }); }; ================================================ FILE: api/routes/tarefas.js ================================================ module.exports = (app) => { const controllerFactory = require("../controllers/tarefas"); const controller = controllerFactory(); app.route("/api/tarefas") .get(async (req, res, next) => { try { await controller.findAll(req, res); } catch (err) { next(err); } }) .post(async (req, res, next) => { try { await controller.create(req, res); } catch (err) { next(err); } }); app.route("/api/tarefas/:uuid") .get(async (req, res, next) => { try { await controller.find(req, res); } catch (err) { next(err); } }); app.route("/api/tarefas/update_priority/:uuid") .put(async (req, res, next) => { try { await controller.update_priority(req, res); } catch (err) { next(err); } }); app.route("/api/tarefas/:uuid") .delete(async (req, res, next) => { try { await controller.delete(req, res); } catch (err) { next(err); } }); }; ================================================ FILE: api/routes/versao.js ================================================ module.exports = (app) => { const controller = require("../controllers/versao")(); app.route("/api/versao").get(controller.get); }; ================================================ FILE: buildspec.yml ================================================ version: 0.2 phases: pre_build: commands: - echo Fazendo login no ECR... - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 380278406175.dkr.ecr.us-east-1.amazonaws.com - REPOSITORY_URI=380278406175.dkr.ecr.us-east-1.amazonaws.com/bia - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - IMAGE_TAG=${COMMIT_HASH:=latest} build: commands: - echo Build iniciado em `date` - echo Gerando imagem da BIA... - docker build -t $REPOSITORY_URI:latest . - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG post_build: commands: - echo Build finalizado com sucesso em `date` - echo Fazendo push da imagem para o ECR... - docker push $REPOSITORY_URI:latest - docker push $REPOSITORY_URI:$IMAGE_TAG - echo Gerando artefato da imagem para o ECS - printf '[{"name":"bia","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json artifacts: files: imagedefinitions.json ================================================ FILE: client/db.json ================================================ { "tasks": [ { "id": 1, "title": "Doctor Appoiment", "day": "Feb 5th at 2:30pm", "reminder": true }, { "title": "Party 2", "day": "16 de abril", "reminder": true, "id": 4 }, { "title": "Party 3", "day": "18 de Abril", "reminder": true, "id": 5 } ] } ================================================ FILE: client/index.html ================================================ BIA + IA - 2026
================================================ FILE: client/package.json ================================================ { "name": "react-task-tracker", "version": "0.1.0", "type": "module", "private": true, "dependencies": { "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "json-server": "^1.0.0-beta.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", "react-router-dom": "^6.28.0", "web-vitals": "^4.2.4" }, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", "server": "json-server --watch db.json --port 5000" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@vitejs/plugin-react": "^4.5.2", "vite": "^5.4.19" } } ================================================ FILE: client/public/manifest.json ================================================ { "short_name": "BIA", "name": "BIA - Bot Inteligente de Atividades", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: client/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: client/src/App.jsx ================================================ import React, { useState, useEffect } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { ThemeProvider } from "./contexts/ThemeContext.jsx"; import { LogProvider, useLog } from "./contexts/LogContext.jsx"; import Header from "./components/Header.jsx"; import Footer from "./components/Footer.jsx"; import Tasks from "./components/Tasks.jsx"; import AddTask from "./components/AddTask.jsx"; import About from "./components/About.jsx"; import DebugLogs from "./components/DebugLogs.jsx"; const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:8080"; function AppContent() { const [tasks, setTasks] = useState([]); const { logApiRequest, logApiResponse, logApiError, addLog } = useLog(); useEffect(() => { addLog('INFO', 'Aplicação iniciada', `API URL configurada: ${apiUrl}`); getTasks(); }, []); const getTasks = async () => { try { const tasksFromServer = await fetchTasks(); setTasks(tasksFromServer); } catch (error) { addLog('ERROR', 'Falha ao carregar tarefas', error.message); } }; //Listar Tarefas const fetchTasks = async () => { const url = `${apiUrl}/api/tarefas`; logApiRequest('GET', url); try { const res = await fetch(url); const data = await res.json(); logApiResponse('GET', url, res.status, data); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } return data; } catch (error) { logApiError('GET', url, error); throw error; } }; //Listar Tarefa const fetchTask = async (uuid) => { const url = `${apiUrl}/api/tarefas/${uuid}`; logApiRequest('GET', url); try { const res = await fetch(url); const data = await res.json(); logApiResponse('GET', url, res.status, data); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } return data; } catch (error) { logApiError('GET', url, error); throw error; } }; //Alternar Importante const toggleReminder = async (uuid) => { try { const taskToToggle = await fetchTask(uuid); const updatedTask = { ...taskToToggle, importante: !taskToToggle.importante, }; const url = `${apiUrl}/api/tarefas/update_priority/${uuid}`; logApiRequest('PUT', url, updatedTask); const res = await fetch(url, { method: "PUT", headers: { "Content-type": "application/json", }, body: JSON.stringify(updatedTask), }); const data = await res.json(); logApiResponse('PUT', url, res.status, data); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } setTasks( tasks.map((task) => task.uuid === uuid ? { ...task, importante: data.importante } : task ) ); addLog('SUCCESS', 'Prioridade alterada', `Tarefa ${uuid} - Importante: ${data.importante}`); } catch (error) { addLog('ERROR', 'Falha ao alterar prioridade', error.message); } }; //Adicionar Tarefa const addTask = async (task) => { const url = `${apiUrl}/api/tarefas`; logApiRequest('POST', url, task); try { const res = await fetch(url, { method: "POST", headers: { "Content-type": "application/json", }, body: JSON.stringify(task), }); const data = await res.json(); logApiResponse('POST', url, res.status, data); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } setTasks([...tasks, data]); addLog('SUCCESS', 'Tarefa criada', `"${task.titulo}" adicionada com sucesso`); } catch (error) { logApiError('POST', url, error); addLog('ERROR', 'Falha ao criar tarefa', error.message); } }; //Remover tarefa const deleteTask = async (uuid) => { const url = `${apiUrl}/api/tarefas/${uuid}`; logApiRequest('DELETE', url); try { const res = await fetch(url, { method: "DELETE", }); logApiResponse('DELETE', url, res.status); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } setTasks(tasks.filter((task) => task.uuid !== uuid)); addLog('SUCCESS', 'Tarefa removida', `Tarefa ${uuid} excluída com sucesso`); } catch (error) { logApiError('DELETE', url, error); addLog('ERROR', 'Falha ao excluir tarefa', error.message); } }; // Componente para página principal const HomePage = () => ( <> {tasks.length > 0 ? ( ) : (

Nenhuma tarefa por aqui 📝

Adicione sua primeira tarefa usando o formulário acima!

)} ); return (
} /> } />
); } function App() { return ( ); } export default App; ================================================ FILE: client/src/components/About.js ================================================ import React from "react"; import { Link } from "react-router-dom"; import DadosHenrylle from "./DadosHenrylle"; const About = () => { return (

Versão 4.2.0

BIA 23/05 e 24/05/2026
Voltar
); }; export default About; ================================================ FILE: client/src/components/About.jsx ================================================ import React from "react"; import { Link } from "react-router-dom"; import DadosHenrylle from "./DadosHenrylle.jsx"; const About = () => { return (

Próximo Evento

AWS & IA

23/05 e 24/05/2026
Formação AWS

← Voltar
); }; export default About; ================================================ FILE: client/src/components/AddTask.jsx ================================================ import React, { useState } from "react"; import Modal from "./Modal"; const AddTask = ({ onAdd }) => { const [titulo, setTitulo] = useState(""); const [dia, setDia] = useState(""); const [importante, setImportante] = useState(false); const [showModal, setShowModal] = useState(false); const onSubmit = (e) => { e.preventDefault(); if (!titulo.trim()) { setShowModal(true); return; } onAdd({ titulo: titulo.trim(), dia_atividade: dia || new Date().toLocaleDateString('pt-BR'), importante }); setTitulo(""); setDia(""); setImportante(false); }; return (
setTitulo(e.target.value)} />
setDia(e.target.value)} />
setImportante(e.target.checked)} />
setShowModal(false)} title="Campo obrigatório" message="Por favor, adicione uma descrição para a tarefa" type="warning" /> ); }; export default AddTask; ================================================ FILE: client/src/components/Button.jsx ================================================ import React from "react"; const Button = ({ color, text, onClick }) => { return ( ); }; Button.defaultProps = { color: "", }; export default Button; ================================================ FILE: client/src/components/DadosHenrylle.jsx ================================================ import React from "react"; const DadosHenrylle = () => { return ( ); }; export default DadosHenrylle; ================================================ FILE: client/src/components/DebugLogs.jsx ================================================ import React from 'react'; import { FaBug, FaTimes, FaTrash, FaChevronDown, FaChevronUp } from 'react-icons/fa'; import { useLog } from '../contexts/LogContext.jsx'; const DebugLogs = () => { const { logs, isLogVisible, debugMode, clearLogs, toggleLogVisibility } = useLog(); if (!debugMode) return null; const getLogIcon = (type) => { switch (type) { case 'ERROR': return '🔴'; case 'SUCCESS': return '🟢'; case 'WARNING': return '🟡'; default: return '🔵'; } }; const getLogClass = (type) => { switch (type) { case 'ERROR': return 'log-error'; case 'SUCCESS': return 'log-success'; case 'WARNING': return 'log-warning'; default: return 'log-info'; } }; return (
{isLogVisible && ( )}
{isLogVisible && (

🔧 Área de Debug

Esta área mostra logs da API para facilitar o debug durante o desenvolvimento.

URL da API: {import.meta.env.VITE_API_URL || "http://localhost:8080"}

{logs.length === 0 ? (

Nenhum log ainda. Interaja com a aplicação para ver os logs aparecerem aqui.

) : ( logs.map((log) => (
{getLogIcon(log.type)} {log.timestamp} {log.type} {log.message}
{log.details && (
{log.details}
)}
)) )}
)}
); }; export default DebugLogs; ================================================ FILE: client/src/components/Footer.jsx ================================================ import React from "react"; import { Link } from "react-router-dom"; const Footer = () => { return (

Formação AWS 2026

Sobre a BIA
); }; export default Footer; ================================================ FILE: client/src/components/Header.jsx ================================================ import React from "react"; import { FaSun, FaMoon } from "react-icons/fa"; import { useTheme } from "../contexts/ThemeContext.jsx"; import VersionInfo from "./VersionInfo"; const Header = ({ title }) => { const { isDarkMode, toggleTheme } = useTheme(); return (

{title}

); }; Header.defaultProps = { title: "BIA 2026", }; export default Header; ================================================ FILE: client/src/components/Modal.jsx ================================================ import React from 'react'; const Modal = ({ isOpen, onClose, title, message, type = 'info' }) => { if (!isOpen) return null; const getIcon = () => { switch (type) { case 'error': return '⚠️'; case 'success': return '✅'; case 'warning': return '⚠️'; default: return 'ℹ️'; } }; const getTypeClass = () => { switch (type) { case 'error': return 'modal-error'; case 'success': return 'modal-success'; case 'warning': return 'modal-warning'; default: return 'modal-info'; } }; return (
e.stopPropagation()}>
{getIcon()}

{title || 'Atenção'}

{message}

); }; export default Modal; ================================================ FILE: client/src/components/Task.jsx ================================================ import React from "react"; import { FaTimes, FaStar, FaRegStar } from "react-icons/fa"; const Task = ({ task, onDelete, onToggle }) => { return (
onToggle(task.uuid)} >

{task.titulo}

📅 {task.dia_atividade || "Sem data definida"}

); }; export default Task; ================================================ FILE: client/src/components/Tasks.jsx ================================================ import React, { useState, useEffect } from "react"; import Task from "./Task.jsx"; const Tasks = ({ tasks, onDelete, onToggle }) => { const [currentPage, setCurrentPage] = useState(1); const tasksPerPage = 5; // Mostrar 5 tarefas por página // Calcular tarefas da página atual const indexOfLastTask = currentPage * tasksPerPage; const indexOfFirstTask = indexOfLastTask - tasksPerPage; const currentTasks = tasks.slice(indexOfFirstTask, indexOfLastTask); // Calcular total de páginas const totalPages = Math.ceil(tasks.length / tasksPerPage); // Resetar para primeira página quando tasks mudam useEffect(() => { if (currentPage > totalPages && totalPages > 0) { setCurrentPage(1); } }, [tasks.length, totalPages, currentPage]); // Funções de navegação const goToPage = (pageNumber) => { setCurrentPage(pageNumber); }; const goToPrevious = () => { if (currentPage > 1) { setCurrentPage(currentPage - 1); } }; const goToNext = () => { if (currentPage < totalPages) { setCurrentPage(currentPage + 1); } }; // Se não há tarefas, não mostrar nada if (tasks.length === 0) { return null; } return (
{/* Lista de tarefas da página atual */}
{currentTasks.map((task) => ( ))}
{/* Controles de paginação */} {totalPages > 1 && (
Mostrando {indexOfFirstTask + 1}-{Math.min(indexOfLastTask, tasks.length)} de {tasks.length} tarefas
{/* Números das páginas */} {Array.from({ length: totalPages }, (_, index) => { const pageNumber = index + 1; // Mostrar sempre primeira, última e páginas próximas da atual if ( pageNumber === 1 || pageNumber === totalPages || (pageNumber >= currentPage - 1 && pageNumber <= currentPage + 1) ) { return ( ); } // Mostrar reticências if ( pageNumber === currentPage - 2 || pageNumber === currentPage + 2 ) { return ...; } return null; })}
)}
); }; export default Tasks; ================================================ FILE: client/src/components/VersionInfo.jsx ================================================ import React, { useState, useEffect } from 'react'; const VersionInfo = () => { const [showVersion, setShowVersion] = useState(false); const [apiStatus, setApiStatus] = useState('checking'); // 'checking', 'online', 'offline' const [apiVersion, setApiVersion] = useState('4.0.0'); const getApiUrl = () => { // Se estiver definido no ambiente (Docker/Produção) if (import.meta.env.VITE_API_URL) { return import.meta.env.VITE_API_URL; } // Se estiver rodando no mesmo domínio (produção integrada) if (window.location.port === '8080') { return window.location.origin; } // Desenvolvimento local - inferir porta 8080 return 'http://localhost:8080'; }; const checkApiHealth = async () => { setApiStatus('checking'); try { const apiUrl = getApiUrl(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout const response = await fetch(`${apiUrl}/api/versao`, { signal: controller.signal, method: 'GET', cache: 'no-cache' }); clearTimeout(timeoutId); if (response.ok) { const versionText = await response.text(); setApiVersion(versionText); setApiStatus('online'); } else { setApiStatus('offline'); } } catch (error) { console.warn('API Health Check falhou:', error.message); setApiStatus('offline'); } }; useEffect(() => { checkApiHealth(); // Recheck a cada 30 segundos const interval = setInterval(checkApiHealth, 30000); return () => clearInterval(interval); }, []); const handleVersionClick = () => { setShowVersion(!showVersion); if (!showVersion) { // Recheca quando abre o tooltip checkApiHealth(); } }; const openVersionEndpoint = () => { const apiUrl = getApiUrl(); window.open(`${apiUrl}/api/versao`, '_blank'); }; const getStatusIcon = () => { switch (apiStatus) { case 'online': return '🟢'; case 'offline': return '🔴'; case 'checking': return '🟡'; default: return '⚪'; } }; const getStatusText = () => { switch (apiStatus) { case 'online': return 'Online'; case 'offline': return 'Offline'; case 'checking': return 'Verificando...'; default: return 'Desconhecido'; } }; const getEnvironmentInfo = () => { const { protocol, hostname, port } = window.location; // Detectar tipo de ambiente if (hostname === 'localhost' || hostname === '127.0.0.1') { return { type: 'local', icon: '🏠', label: 'Local', description: `${hostname}:${port}`, color: '#3b82f6' // azul }; } // IP direto sem HTTPS if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname) && protocol === 'http:') { return { type: 'ip-http', icon: '🌐', label: 'IP Direto', description: `${hostname}${port ? ':' + port : ''}`, color: '#f59e0b' // amarelo/laranja }; } // ALB/Load Balancer sem HTTPS if (protocol === 'http:' && hostname.includes('.elb.')) { return { type: 'alb-http', icon: '⚖️', label: 'ALB HTTP', description: hostname, color: '#ef4444' // vermelho }; } // Domínio com HTTPS (produção) if (protocol === 'https:') { return { type: 'domain-https', icon: '🔒', label: 'Produção', description: hostname, color: '#22c55e' // verde }; } // Outros casos return { type: 'other', icon: '❓', label: 'Outro', description: `${hostname}${port ? ':' + port : ''}`, color: '#6b7280' // cinza }; }; return (
{showVersion && (
{apiVersion}
{getStatusIcon()} Status: {getStatusText()} {getEnvironmentInfo().icon} Ambiente: {getEnvironmentInfo().label} Local: {getEnvironmentInfo().description} API: {getApiUrl()}
)}
); }; export default VersionInfo; ================================================ FILE: client/src/contexts/LogContext.jsx ================================================ import React, { createContext, useContext, useState, useEffect } from 'react'; const LogContext = createContext(); export const useLog = () => { const context = useContext(LogContext); if (!context) { throw new Error('useLog deve ser usado dentro de um LogProvider'); } return context; }; export const LogProvider = ({ children }) => { const [logs, setLogs] = useState([]); const [isLogVisible, setIsLogVisible] = useState(false); const [debugMode, setDebugMode] = useState(false); useEffect(() => { // Verifica se o modo debug está habilitado via environment variable const debugEnabled = import.meta.env.VITE_DEBUG_MODE === 'true'; setDebugMode(debugEnabled); if (debugEnabled) { addLog('INFO', 'Modo debug habilitado', 'Sistema inicializado com logs visíveis'); } }, []); const addLog = (type, message, details = null) => { const timestamp = new Date().toLocaleTimeString('pt-BR'); const logEntry = { id: Date.now() + Math.random(), timestamp, type, // INFO, ERROR, SUCCESS, WARNING message, details, }; setLogs(prev => [logEntry, ...prev].slice(0, 50)); // Mantém apenas os 50 logs mais recentes // Log no console também const logMethod = type === 'ERROR' ? 'error' : type === 'WARNING' ? 'warn' : 'log'; console[logMethod](`[${timestamp}] ${type}: ${message}`, details || ''); }; const clearLogs = () => { setLogs([]); addLog('INFO', 'Logs limpos', 'Histórico de logs foi resetado'); }; const toggleLogVisibility = () => { setIsLogVisible(prev => !prev); }; // Função para interceptar e logar requests da API const logApiRequest = (method, url, payload = null) => { addLog('INFO', `API ${method}`, `${url}${payload ? ' | Payload: ' + JSON.stringify(payload) : ''}`); }; const logApiResponse = (method, url, status, data = null) => { const type = status >= 200 && status < 300 ? 'SUCCESS' : 'ERROR'; const message = `API ${method} - ${status}`; const details = `${url} | Response: ${data ? JSON.stringify(data).substring(0, 100) : 'Sem dados'}`; addLog(type, message, details); }; const logApiError = (method, url, error) => { addLog('ERROR', `API ${method} FALHOU`, `${url} | Erro: ${error.message}`); }; return ( {children} ); }; ================================================ FILE: client/src/contexts/ThemeContext.jsx ================================================ import React, { createContext, useContext, useState, useEffect } from 'react'; const ThemeContext = createContext(); export const useTheme = () => { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme deve ser usado dentro de um ThemeProvider'); } return context; }; export const ThemeProvider = ({ children }) => { const [isDarkMode, setIsDarkMode] = useState(() => { const savedTheme = localStorage.getItem('theme'); return savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches); }); useEffect(() => { localStorage.setItem('theme', isDarkMode ? 'dark' : 'light'); document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light'); }, [isDarkMode]); const toggleTheme = () => { setIsDarkMode(prev => !prev); }; return ( {children} ); }; ================================================ FILE: client/src/index.css ================================================ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); /* Variáveis de tema */ :root { /* Tema Light */ --bg-primary: #ffffff; --bg-secondary: #f8fafc; --bg-card: #ffffff; --text-primary: #1f2937; --text-secondary: #6b7280; --border-color: #e5e7eb; --accent-primary: #3b82f6; --accent-success: #10b981; --accent-danger: #ef4444; --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); } [data-theme="dark"] { /* Tema Dark */ --bg-primary: #111827; --bg-secondary: #1f2937; --bg-card: #1f2937; --text-primary: #f9fafb; --text-secondary: #d1d5db; --border-color: #374151; --accent-primary: #60a5fa; --accent-success: #34d399; --accent-danger: #f87171; --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3); } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.5; transition: background-color 0.2s ease, color 0.2s ease; } .app { min-height: 100vh; background: var(--bg-primary); padding: 1rem; } .container { max-width: 480px; margin: 0 auto; background: var(--bg-card); border-radius: 8px; box-shadow: var(--shadow); border: 1px solid var(--border-color); overflow: hidden; } /* Header */ .header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; border-bottom: 1px solid var(--border-color); background: var(--bg-card); } .header h1 { font-size: 1.5rem; font-weight: 600; color: var(--text-primary); } .theme-controls { display: flex; align-items: center; gap: 0.5rem; } .theme-toggle { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 6px; padding: 0.5rem; cursor: pointer; transition: all 0.2s ease; color: var(--text-secondary); } .theme-toggle:hover { background: var(--bg-primary); color: var(--text-primary); } /* Buttons */ .btn { display: inline-flex; align-items: center; gap: 0.5rem; background: var(--accent-primary); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: all 0.2s ease; } .btn:hover { opacity: 0.9; } .btn.danger { background: var(--accent-danger); } .btn.success { background: var(--accent-success); } .btn-block { width: 100%; justify-content: center; } /* Tasks */ .task { display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1.5rem; border-bottom: 1px solid var(--border-color); background: var(--bg-card); transition: background-color 0.2s ease; } .task:hover { background: var(--bg-secondary); } .task.reminder { border-left: 3px solid var(--accent-success); } .task-content { flex: 1; } .task h3 { font-size: 0.875rem; font-weight: 500; color: var(--text-primary); margin-bottom: 0.25rem; } .task p { font-size: 0.75rem; color: var(--text-secondary); } .task-actions { display: flex; gap: 0.25rem; } .task-priority, .task-delete { background: none; border: none; cursor: pointer; padding: 0.25rem; border-radius: 4px; font-size: 0.875rem; transition: all 0.2s ease; } .task-priority { color: var(--text-secondary); } .task-priority:hover, .task.reminder .task-priority { color: var(--accent-success); } .task-delete { color: var(--text-secondary); } .task-delete:hover { color: var(--accent-danger); } /* Add Form */ .add-form { padding: 1.5rem; background: var(--bg-secondary); border-bottom: 1px solid var(--border-color); } .form-control { margin-bottom: 1rem; } .form-control:last-child { margin-bottom: 0; } .form-control label { display: block; font-size: 0.875rem; font-weight: 500; color: var(--text-primary); margin-bottom: 0.25rem; } .form-control input { width: 100%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 6px; font-size: 0.875rem; background: var(--bg-card); color: var(--text-primary); transition: border-color 0.2s ease; } .form-control input:focus { outline: none; border-color: var(--accent-primary); } .form-control-check { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; } .form-control-check input[type="checkbox"] { width: auto; margin: 0; } .form-control-check label { margin: 0; font-size: 0.875rem; cursor: pointer; } /* Footer */ footer { padding: 1rem 1.5rem; text-align: center; border-top: 1px solid var(--border-color); background: var(--bg-card); } .footer-content { display: flex; flex-direction: column; gap: 0.5rem; align-items: center; } .footer-content p { font-size: 0.75rem; color: var(--text-secondary); margin: 0; } .footer-link { font-size: 0.75rem; color: var(--accent-primary); text-decoration: none; font-weight: 500; } .footer-link:hover { text-decoration: underline; } /* Empty State */ .empty-state { text-align: center; padding: 2rem 1.5rem; color: var(--text-secondary); } .empty-state h3 { font-size: 1rem; font-weight: 500; color: var(--text-primary); margin-bottom: 0.5rem; } .empty-state p { font-size: 0.875rem; } /* About Page */ .about-page { padding: 1.5rem; display: flex; flex-direction: column; min-height: 100%; } .about-content { flex: 1; } .about-footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border-color); } .about-header { text-align: center; margin-bottom: 2rem; } .about-hero { background: linear-gradient(135deg, var(--accent-primary), var(--accent-success)); color: white; padding: 2rem; border-radius: 8px; margin-bottom: 1.5rem; } .about-hero h2 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; } .about-subtitle { font-size: 0.875rem; opacity: 0.9; } .feature-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; margin-bottom: 2rem; } .feature-card { background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 8px; padding: 1.5rem; text-align: center; } .feature-card.highlight { border-color: var(--accent-primary); background: var(--bg-secondary); } .feature-card h3 { font-size: 1rem; font-weight: 600; color: var(--text-primary); margin-bottom: 0.5rem; } .feature-card h4 { font-size: 1.25rem; font-weight: 600; color: var(--accent-primary); margin-bottom: 0.5rem; } .feature-card p { font-size: 0.875rem; color: var(--text-secondary); line-height: 1.4; } .back-button { display: inline-flex; align-items: center; gap: 0.5rem; color: var(--accent-primary); text-decoration: none; font-weight: 500; padding: 0.75rem 1.25rem; border: 1px solid var(--accent-primary); border-radius: 8px; font-size: 0.875rem; transition: all 0.2s ease; align-self: flex-start; } .back-button:hover { background: var(--accent-primary); color: white; } /* Links Cards */ .dados-henrylle { margin-top: 2rem; } .dados-henrylle h3 { font-size: 1rem; font-weight: 600; color: var(--text-primary); margin-bottom: 1rem; text-align: center; } .links-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.75rem; } .link-card { display: block; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem; text-decoration: none; transition: all 0.2s ease; text-align: center; } .link-card:hover { border-color: var(--accent-primary); background: var(--bg-secondary); transform: translateY(-1px); box-shadow: var(--shadow); } .link-card h4 { font-size: 0.875rem; font-weight: 600; color: var(--text-primary); margin-bottom: 0.25rem; } .link-card p { font-size: 0.75rem; color: var(--text-secondary); margin: 0; } /* Responsive */ @media (max-width: 640px) { .app { padding: 0.5rem; } .container { border-radius: 0; border-left: none; border-right: none; } .header { padding: 1rem; } .header h1 { font-size: 1.25rem; } .links-grid { grid-template-columns: 1fr; } } /* Header Controls */ .header-controls { display: flex; align-items: center; gap: 0.5rem; } /* Version Info */ .version-info { position: relative; } .version-trigger { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 0.75rem; font-weight: 500; color: var(--text-secondary); transition: all 0.2s ease; } .version-trigger.online { border-color: #22c55e; } .version-trigger.offline { border-color: #ef4444; } .version-trigger.checking { border-color: #f59e0b; } .version-trigger:hover { background: var(--bg-primary); color: var(--text-primary); border-color: var(--accent-primary); } .version-tooltip { position: absolute; top: 100%; right: 0; margin-top: 0.5rem; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 6px; padding: 0.75rem; box-shadow: var(--shadow); min-width: 160px; z-index: 1000; } .version-content strong { display: block; color: var(--text-primary); font-size: 0.875rem; margin-bottom: 0.5rem; } .version-details { display: flex; flex-direction: column; gap: 0.25rem; } .version-details small { color: var(--text-secondary); font-size: 0.75rem; } .version-link { background: none; border: none; color: var(--accent-primary); cursor: pointer; font-size: 0.75rem; text-decoration: underline; padding: 0; } .version-link:hover { opacity: 0.8; } .version-link.refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; } .version-details .status-indicator { margin-right: 0.25rem; } .version-details .env-indicator { margin-right: 0.25rem; font-weight: 600; } /* Modal */ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px); } .modal-content { background: var(--bg-card); border-radius: 8px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); border: 1px solid var(--border-color); max-width: 400px; width: 90%; max-height: 90vh; overflow: hidden; animation: modalSlideIn 0.2s ease-out; } @keyframes modalSlideIn { from { opacity: 0; transform: translateY(-20px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } .modal-header { display: flex; align-items: center; gap: 0.75rem; padding: 1rem 1.5rem; border-bottom: 1px solid var(--border-color); } .modal-icon { font-size: 1.25rem; } .modal-title { font-size: 1rem; font-weight: 600; color: var(--text-primary); margin: 0; } .modal-body { padding: 1rem 1.5rem; } .modal-body p { color: var(--text-secondary); font-size: 0.875rem; line-height: 1.5; margin: 0; } .modal-footer { padding: 1rem 1.5rem; border-top: 1px solid var(--border-color); display: flex; justify-content: flex-end; } .modal-btn { background: var(--accent-primary); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: all 0.2s ease; } .modal-btn:hover { opacity: 0.9; } .modal-error .modal-header { border-bottom-color: var(--accent-danger); } .modal-error .modal-btn { background: var(--accent-danger); } .modal-success .modal-header { border-bottom-color: var(--accent-success); } .modal-success .modal-btn { background: var(--accent-success); } .modal-warning .modal-header { border-bottom-color: #f59e0b; } .modal-warning .modal-btn { background: #f59e0b; } /* Paginação */ .tasks-container { display: flex; flex-direction: column; } .tasks-list { flex: 1; } .pagination { padding: 1rem 1.5rem; border-top: 1px solid var(--border-color); background: var(--bg-secondary); display: flex; flex-direction: column; gap: 0.75rem; } .pagination-info { text-align: center; } .pagination-info span { font-size: 0.75rem; color: var(--text-secondary); } .pagination-controls { display: flex; justify-content: center; align-items: center; gap: 0.25rem; } .pagination-btn { background: var(--bg-card); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.5rem 0.75rem; border-radius: 6px; cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: all 0.2s ease; min-width: 2.5rem; height: 2.5rem; display: flex; align-items: center; justify-content: center; } .pagination-btn:hover:not(:disabled) { background: var(--bg-primary); border-color: var(--accent-primary); color: var(--accent-primary); } /* Feedback visual para toque em mobile */ .pagination-btn:active:not(:disabled) { transform: scale(0.95); background: var(--accent-primary); color: white; transition: all 0.1s ease; } /* Melhor contraste para toque */ @media (hover: none) and (pointer: coarse) { .pagination-btn { border-width: 2px; } .pagination-btn:not(.active):not(:disabled) { background: var(--bg-primary); border-color: var(--border-color); } .pagination-btn:active:not(:disabled) { background: var(--accent-primary); border-color: var(--accent-primary); color: white; } } .pagination-btn.active { background: var(--accent-primary); color: white; border-color: var(--accent-primary); } .pagination-btn:disabled { opacity: 0.4; cursor: not-allowed; } .pagination-dots { padding: 0.5rem 0.25rem; color: var(--text-secondary); font-weight: 500; } @media (max-width: 640px) { .pagination { padding: 0.75rem 1rem; gap: 1rem; } .pagination-controls { gap: 0.5rem; } .pagination-btn { padding: 0.75rem; min-width: 3rem; height: 3rem; font-size: 0.875rem; border-radius: 8px; /* Área de toque maior para mobile */ touch-action: manipulation; } /* Botões de navegação (anterior/próximo) maiores no mobile */ .pagination-btn:first-child, .pagination-btn:last-child { min-width: 3.5rem; font-size: 1.25rem; font-weight: 600; } .pagination-info span { font-size: 0.75rem; } /* Esconder números de página no mobile muito pequeno */ @media (max-width: 480px) { .pagination-btn:not(:first-child):not(:last-child):not(.active) { display: none; } .pagination-dots { display: none; } /* Mostrar apenas página atual entre os botões de navegação */ .pagination-controls { justify-content: space-between; padding: 0 0.5rem; } .pagination-btn.active { order: 0; position: absolute; left: 50%; transform: translateX(-50%); } } } ================================================ FILE: client/src/main.jsx ================================================ import React from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; import App from './App.jsx'; import reportWebVitals from './reportWebVitals'; const container = document.getElementById('root'); const root = createRoot(container); root.render( ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); ================================================ FILE: client/src/reportWebVitals.js ================================================ const reportWebVitals = onPerfEntry => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); getLCP(onPerfEntry); getTTFB(onPerfEntry); }); } }; export default reportWebVitals; ================================================ FILE: client/vite.config.js ================================================ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], server: { port: 3001, open: true, }, build: { outDir: 'build', sourcemap: true, }, define: { 'process.env': {}, }, }); ================================================ FILE: compose.yml ================================================ services: server: build: . container_name: bia ports: - 3001:8080 depends_on: - database environment: DB_USER: postgres DB_PWD: postgres DB_HOST: database DB_PORT: 5432 ## NAO PRECISA NO BOOTCAMP DAQUI PRA BAIXO ## # DB_SECRET_NAME: # DB_REGION: # AWS_ACCESS_KEY_ID: # AWS_SECRET_ACCESS_KEY: # DEBUG_SECRET: # IS_LOCAL: true # healthcheck: # test: ["CMD", "curl", "-f", "http://localhost:8080/api/versao"] # interval: 10s # timeout: 5s # retries: 3 # start_period: 5s database: image: postgres:17.1 container_name: database environment: - "POSTGRES_USER=postgres" - "POSTGRES_PASSWORD=postgres" - "POSTGRES_DB=bia" ports: - 5433:5432 volumes: - db:/var/lib/postgresql/data volumes: db: ================================================ FILE: config/database.js ================================================ const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager"); const { fromIni, fromEnv } = require("@aws-sdk/credential-providers"); const { STSClient, GetCallerIdentityCommand } = require("@aws-sdk/client-sts"); async function isLocalConnection() { // Lógica para determinar se a conexão é local return ( process.env.DB_HOST === undefined || process.env.DB_HOST === "database" || process.env.DB_HOST === "127.0.0.1" || process.env.DB_HOST === "localhost" ); } async function getRemoteDialectOptions() { // Configurações específicas para conexões remotas (útil a partir do pg 15) return { ssl: { require: true, rejectUnauthorized: false, }, }; } async function getConfig(){ let dbConfig = { username: process.env.DB_USER || "postgres", password: process.env.DB_PWD || "postgres", database: "bia", host: process.env.DB_HOST || "127.0.0.1", port: process.env.DB_PORT || 5433, dialect: "postgres", dialectOptions: await isLocalConnection() ? {} : await getRemoteDialectOptions(), }; if(process.env.DB_SECRET_NAME && process.env.DB_SECRET_NAME.trim() !== '' ){ const secretsManagerClient = await createSecretsManagerClient(); const secrets = await getSecrets(secretsManagerClient); if(secrets){ dbConfig.username = secrets.username; dbConfig.password = secrets.password; await imprimirSecrets(secrets); } } return dbConfig; } async function createSecretsManagerClient() { // Verifica se a variável de ambiente está definida e não está vazia let credentials; if (process.env.IS_LOCAL === "true") { credentials = fromEnv(); //credentials = fromIni({ profile: "SEU_PROFILE" }); } if (process.env.DB_SECRET_NAME) { // Instancia o cliente do Secrets Manager const client = new SecretsManagerClient({ region: process.env.DB_REGION, credentials }); if(process.env.DEBUG_SECRET === "true"){ const stsClient = new STSClient({ region: process.env.DB_REGION, credentials }); try { const identity = await stsClient.send(new GetCallerIdentityCommand({})); console.log('Credenciais carregadas com sucesso:', identity); console.log('Account ID:', identity.Account); } catch (error) { console.error('Erro ao carregar credenciais:', error); } } return client; } else { console.log('DB_SECRET_NAME não está definida. Se for usar secrets, informe também DB_REGION.'); return null; } } async function imprimirSecrets(secrets){ if(process.env.DEBUG_SECRET === "true") console.log(secrets); } async function getSecrets(secretsManagerClient) { try { if (!secretsManagerClient) { console.error('O cliente do Secrets Manager não foi instanciado.'); return; } console.log(`Vou trabalhar com o secrets ${process.env.DB_SECRET_NAME}`); const command = new GetSecretValueCommand({ SecretId: process.env.DB_SECRET_NAME }); const data = await secretsManagerClient.send(command); if ('SecretString' in data) { return JSON.parse(data.SecretString); } else { return Buffer.from(data.SecretBinary, 'base64'); } } catch (err) { console.error('Erro ao recuperar as credenciais do Secrets Manager:', err); throw err; } } module.exports = getConfig; ================================================ FILE: config/default.json ================================================ { "server": { "port": 8080 } } ================================================ FILE: config/express.js ================================================ const express = require("express"); var cors = require("cors"); var path = require("path"); const config = require("config"); var bodyParser = require("body-parser"); module.exports = () => { const app = express(); // SETANDO VARIÁVEIS DA APLICAÇÃO app.set("port", process.env.PORT || config.get("server.port")); //Setando react app.use(express.static(path.join(__dirname, "../", "client", "build"))); // parse request bodies (req.body) app.use(express.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(cors()); require("../api/routes/tarefas")(app); require("../api/routes/versao")(app); // Fallback para React Router - serve index.html para todas as rotas não-API app.get('*', (req, res) => { res.sendFile(path.join(__dirname, "../", "client", "build", "index.html")); }); return app; }; ================================================ FILE: database/migrations/20210924000838-criar-tarefas.js ================================================ "use strict"; module.exports = { up: async (queryInterface, Sequelize) => { return queryInterface.createTable("Tarefas", { uuid: { allowNull: false, primaryKey: true, defaultValue: Sequelize.UUIDV1, type: Sequelize.UUID, }, titulo: { allowNull: false, type: Sequelize.STRING, }, dia_atividade: { allowNull: true, type: Sequelize.STRING, }, importante: { allowNull: true, defaultValue: false, type: Sequelize.BOOLEAN, }, createdAt: { allowNull: false, type: Sequelize.DATE, }, updatedAt: { allowNull: false, type: Sequelize.DATE, }, }); }, down: async (queryInterface, Sequelize) => { await queryInterface.dropTable("Tarefas"); }, }; ================================================ FILE: docs/README.md ================================================ # 📚 Documentação ## 🏗️ Arquitetura AWS - [Diagrama ECS + EC2](./architecture/aws-ecs-diagram.html) - Visualização de uma das arquiteturas propostas para iniciar no treinamento e evoluir na Formação AWS ================================================ FILE: docs/architecture/aws-ecs-diagram.html ================================================ Arquitetura AWS - Projeto BIA com ECS/EC2

🏗️ Arquitetura AWS - Projeto BIA

ECS com EC2 + RDS PostgreSQL + CodePipeline

👥
Usuários
Acesso via navegador web
Interface React (Vite)
🔧
GitHub
Repositório de código
Trigger para CI/CD
⬇️ HTTPS / Git Webhook ⬇️
AWS Region (us-east-1)
⚖️
Application Load Balancer (ALB)
• Distribuição de tráfego entre instâncias ECS
• SSL/TLS termination
• Health checks automáticos
• Target Groups para containers
⬇️ Distribui tráfego ⬇️
VPC (10.0.0.0/16)
Public Subnet A (10.0.1.0/24)
🌐
NAT Gateway
Acesso à internet para subnets privadas
Public Subnet B (10.0.2.0/24)
🌐
NAT Gateway
Redundância para alta disponibilidade
Private Subnet A (10.0.3.0/24)
🐳
ECS Cluster + EC2 Instances
• Auto Scaling Group
• Instâncias t3.micro (2-4 instâncias)
• ECS Agent rodando
• Container BIA (Node.js + React)
📊
CloudWatch Logs
Logs da aplicação e containers
Private Subnet B (10.0.4.0/24)
🐳
ECS Cluster + EC2 Instances
• Réplica para alta disponibilidade
• Mesma configuração da Subnet A
• Load balancing automático
🔐
Secrets Manager
Credenciais do banco de dados
DB Subnet A (10.0.5.0/24)
🗄️
RDS PostgreSQL (Primary)
• PostgreSQL 16
• db.t3.micro (para desenvolvimento)
• Automated backups
• Multi-AZ para produção
DB Subnet B (10.0.6.0/24)
🗄️
RDS PostgreSQL (Standby)
• Réplica síncrona (Multi-AZ)
• Failover automático
• Backup e recuperação
🚀
CI/CD Pipeline
CodePipeline: GitHub → CodeBuild → ECR → ECS Deploy
CodeBuild: Build da imagem Docker + Push para ECR
ECR: Registry privado para imagens Docker
ECS Service: Deploy automático com rolling updates

🛠️ Stack Tecnológica Identificada

Frontend
React 18 + Vite
React Router DOM
React Icons
Backend
Node.js + Express
Sequelize ORM
Morgan (Logging)
Database
PostgreSQL 16
Sequelize Migrations
Connection Pooling
AWS Services
ECS + EC2
RDS PostgreSQL
Secrets Manager
DevOps
Docker
CodePipeline
CodeBuild + ECR
Monitoramento
CloudWatch Logs
Health Checks
Application Insights

✅ Benefícios desta Arquitetura

Alta Disponibilidade
Multi-AZ deployment com failover automático
Escalabilidade
Auto Scaling baseado em CPU/memória
Segurança
Subnets privadas + Secrets Manager
CI/CD Automatizado
Deploy automático via CodePipeline
Monitoramento
CloudWatch integrado + Health checks
Custo Otimizado
EC2 Spot instances + RDS otimizado
================================================ FILE: generate-sts-token.bat ================================================ @echo off REM Script para Windows CMD que chama o script bash REM Uso: generate-sts-token.bat [duration-seconds] [--export-cmd] REM Verifica se Git Bash está disponível where bash >nul 2>nul if %ERRORLEVEL% NEQ 0 ( echo Erro: Git Bash nao encontrado. Instale o Git for Windows. echo Download: https://git-scm.com/download/win exit /b 1 ) REM Executa o script bash if "%3"=="--export-cmd" ( REM Modo export para CMD bash "%~dp0generate-sts-token.sh" %1 %2 --export-cmd ) else ( REM Modo normal bash "%~dp0generate-sts-token.sh" %* ) ================================================ FILE: generate-sts-token.sh ================================================ #!/bin/bash # Script universal para gerar token STS com base no profile AWS # Funciona em: Windows (Git Bash/WSL), macOS e Linux # Uso: ./generate-sts-token.sh [duration-seconds] [--export] # Detecta o sistema operacional detect_os() { case "$(uname -s)" in CYGWIN*|MINGW*|MSYS*) echo "windows" ;; Darwin*) echo "macos" ;; Linux*) echo "linux" ;; *) echo "unknown" ;; esac } # Função para mostrar ajuda show_help() { local os_type=$(detect_os) echo "Uso: $0 [duration-seconds] [--export]" echo "" echo "Sistema detectado: $os_type" echo "" echo "Parâmetros:" echo " profile-name Nome do profile AWS (obrigatório)" echo " duration-seconds Duração em segundos (opcional, padrão: 3600)" echo " --export Exporta as credenciais na sessão atual (opcional)" echo "" echo "Exemplos:" echo " $0 meu-profile # Token de 1 hora" echo " $0 meu-profile 7200 # Token de 2 horas" echo " $0 meu-profile 3600 --export # Token de 1 hora + export automático" echo " $0 meu-profile --export # Token de 1 hora + export automático" echo "" if [[ "$os_type" == "windows" ]]; then echo "Para Windows (Git Bash/PowerShell/CMD):" echo " # Git Bash:" echo " source <($0 meu-profile --export)" echo " # PowerShell:" echo " Invoke-Expression \$(./$0 meu-profile --export-ps)" echo " # CMD:" echo " ./$0 meu-profile --export-cmd > temp.bat && temp.bat && del temp.bat" else echo "Para Unix/Linux/macOS:" echo " source <($0 meu-profile --export)" fi echo "" echo "Nota: Duração mínima é de 15 minutos (900 segundos)" echo " Duração máxima é de 12 horas (43200 segundos)" } # Função para verificar dependências check_dependencies() { local os_type=$(detect_os) local missing_deps=() # Verifica AWS CLI if ! command -v aws &> /dev/null; then missing_deps+=("aws-cli") fi # Verifica jq if ! command -v jq &> /dev/null; then missing_deps+=("jq") fi if [[ ${#missing_deps[@]} -gt 0 ]]; then echo "Erro: Dependências não encontradas: ${missing_deps[*]}" echo "" case "$os_type" in "windows") echo "Para instalar no Windows:" echo " AWS CLI: https://aws.amazon.com/cli/" echo " jq: choco install jq (via Chocolatey) ou baixe de https://stedolan.github.io/jq/" ;; "macos") echo "Para instalar no macOS:" echo " brew install awscli jq" ;; "linux") echo "Para instalar no Linux:" echo " # Ubuntu/Debian:" echo " sudo apt-get install awscli jq" echo " # CentOS/RHEL:" echo " sudo yum install awscli jq" ;; esac exit 1 fi } # Inicializa variáveis PROFILE_NAME="" DURATION=3600 # Default: 1 hora EXPORT_MODE=false EXPORT_PS=false EXPORT_CMD=false # Processa argumentos while [[ $# -gt 0 ]]; do case $1 in --export) EXPORT_MODE=true shift ;; --export-ps) EXPORT_PS=true shift ;; --export-cmd) EXPORT_CMD=true shift ;; --help|-h) show_help exit 0 ;; *) if [[ -z "$PROFILE_NAME" ]]; then PROFILE_NAME="$1" elif [[ "$1" =~ ^[0-9]+$ ]]; then DURATION="$1" else echo "Erro: Argumento inválido '$1'" show_help exit 1 fi shift ;; esac done # Verifica se profile foi informado if [[ -z "$PROFILE_NAME" ]]; then echo "Erro: Profile AWS não informado" echo "" show_help exit 1 fi # Verifica dependências check_dependencies # Valida duração mínima (15 minutos = 900 segundos) if [ "$DURATION" -lt 900 ]; then echo "Erro: Duração mínima é de 15 minutos (900 segundos)" echo "Duração informada: ${DURATION} segundos" echo "Use: $0 ${PROFILE_NAME} 900 (ou maior)" exit 1 fi # Verifica se o profile existe PROFILES=$(aws configure list-profiles 2>/dev/null) if ! echo "${PROFILES}" | grep -q "^${PROFILE_NAME}$"; then echo "Erro: Profile '${PROFILE_NAME}' não encontrado" echo "Profiles disponíveis:" echo "${PROFILES}" exit 1 fi # Detecta sistema operacional OS_TYPE=$(detect_os) # Mostra informações iniciais (exceto em modos de export) if [[ "$EXPORT_MODE" == false && "$EXPORT_PS" == false && "$EXPORT_CMD" == false ]]; then echo "Sistema: $OS_TYPE" echo "Gerando token STS para o profile: ${PROFILE_NAME}" echo "Duração: ${DURATION} segundos" echo "----------------------------------------" fi # Gera o token STS STS_OUTPUT=$(aws sts get-session-token \ --profile "${PROFILE_NAME}" \ --duration-seconds "${DURATION}" \ --output json 2>/dev/null) if [ $? -ne 0 ]; then echo "Erro ao gerar token STS. Verifique suas credenciais e permissões." exit 1 fi # Extrai as credenciais do output ACCESS_KEY=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.AccessKeyId') SECRET_KEY=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.SecretAccessKey') SESSION_TOKEN=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.SessionToken') EXPIRATION=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.Expiration') # Saída baseada no modo e sistema operacional if [[ "$EXPORT_MODE" == true ]]; then # Modo export Unix/Linux/macOS echo "export AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "export AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "export AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "# Credenciais exportadas para a sessão atual!" echo "# Sistema: $OS_TYPE" echo "# Expira em: ${EXPIRATION}" elif [[ "$EXPORT_PS" == true ]]; then # Modo export PowerShell echo "\$env:AWS_ACCESS_KEY_ID='${ACCESS_KEY}'" echo "\$env:AWS_SECRET_ACCESS_KEY='${SECRET_KEY}'" echo "\$env:AWS_SESSION_TOKEN='${SESSION_TOKEN}'" echo "# Credenciais exportadas para PowerShell!" echo "# Expira em: ${EXPIRATION}" elif [[ "$EXPORT_CMD" == true ]]; then # Modo export CMD echo "set AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "set AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "set AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "REM Credenciais exportadas para CMD!" echo "REM Expira em: ${EXPIRATION}" else # Modo normal: mostra todas as informações echo "Token STS gerado com sucesso!" echo "Expira em: ${EXPIRATION}" echo "" echo "=== Credenciais temporárias ===" echo "AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" # Instruções específicas por sistema operacional case "$OS_TYPE" in "windows") echo "=== Para usar no Git Bash ===" echo "export AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "export AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "export AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Para usar no PowerShell ===" echo "\$env:AWS_ACCESS_KEY_ID='${ACCESS_KEY}'" echo "\$env:AWS_SECRET_ACCESS_KEY='${SECRET_KEY}'" echo "\$env:AWS_SESSION_TOKEN='${SESSION_TOKEN}'" echo "" echo "=== Para usar no CMD ===" echo "set AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "set AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "set AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Export automático ===" echo "# Git Bash:" echo "source <($0 ${PROFILE_NAME} ${DURATION} --export)" echo "# PowerShell:" echo "Invoke-Expression \$(./$0 ${PROFILE_NAME} ${DURATION} --export-ps)" echo "# CMD:" echo "$0 ${PROFILE_NAME} ${DURATION} --export-cmd > temp.bat && temp.bat && del temp.bat" ;; *) echo "=== Para usar no terminal ===" echo "export AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "export AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "export AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Export automático ===" echo "source <($0 ${PROFILE_NAME} ${DURATION} --export)" ;; esac echo "" echo "=== Para usar em arquivo .env ===" echo "AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Para salvar em arquivo ===" echo "Para salvar as variáveis em um arquivo:" echo "$0 ${PROFILE_NAME} ${DURATION} > sts-credentials.env" fi ================================================ FILE: index.html ================================================ Hello World Simple App
Hello World!
================================================ FILE: index.js ================================================ var express = require("express"); var logger = require("morgan"); var path = require("path"); var session = require("express-session"); var methodOverride = require("method-override"); var app = express(); // define a custom res.message() method // which stores messages in the session app.response.message = function (msg) { // reference `req.session` via the `this.req` reference var sess = this.req.session; // simply add the msg to an array for later sess.messages = sess.messages || []; sess.messages.push(msg); return this; }; // log app.use(logger("dev")); // serve static files app.use(express.static(path.join(__dirname, "app", "public"))); app.use(express.static(path.join(__dirname, "client", "build"))); // session support app.use( session({ resave: false, // don't save session if unmodified saveUninitialized: false, // don't create session until something stored secret: "some secret here", }) ); // parse request bodies (req.body) app.use(express.urlencoded({ extended: true })); // allow overriding methods in query (?_method=put) app.use(methodOverride("_method")); // expose the "messages" local variable when views are rendered app.use(function (req, res, next) { var msgs = req.session.messages || []; // expose "messages" local variable res.locals.messages = msgs; // expose "hasMessages" res.locals.hasMessages = !!msgs.length; /* This is equivalent: res.locals({ messages: msgs, hasMessages: !! msgs.length }); */ next(); // empty or "flush" the messages so they // don't build up req.session.messages = []; }); // load controllers require("./lib/boot")(app, { verbose: false }); app.use(function (err, req, res, next) { // log it console.error(err.stack); // error page res.status(500).render("5xx"); }); // assume 404 since no middleware responded app.use(function (req, res, next) { res.status(404).render("404", { url: req.originalUrl }); }); app.listen(3000); console.log("Express started on port 3000"); ================================================ FILE: lib/boot.js ================================================ /** * Module dependencies. */ var express = require("express"); var fs = require("fs"); var path = require("path"); module.exports = function (parent, options) { var dir = path.join(__dirname, "..", "app", "controllers"); var verbose = options.verbose; fs.readdirSync(dir).forEach(function (name) { var file = path.join(dir, name); if (!fs.statSync(file).isDirectory()) return; verbose && console.log("\n %s:", name); var obj = require(file); var name = obj.name || name; var prefix = obj.prefix || ""; var app = express(); var handler; var method; var url; // allow specifying the view engine if (obj.engine) app.set("view engine", obj.engine); app.set( "views", path.join(__dirname, "..", "app", "controllers", name, "views") ); // generate routes based // on the exported methods for (var key in obj) { // "reserved" exports if (~["name", "prefix", "engine", "before"].indexOf(key)) continue; // route exports switch (key) { case "show": method = "get"; url = "/" + name + "/:" + name + "_id"; break; case "list": method = "get"; url = "/" + name + "s"; break; case "edit": method = "get"; url = "/" + name + "/:" + name + "_id/edit"; break; case "update": method = "put"; url = "/" + name + "/:" + name + "_id"; break; case "create": method = "post"; url = "/" + name; break; case "index": method = "get"; url = "/"; break; default: /* istanbul ignore next */ throw new Error("unrecognized route: " + name + "." + key); } // setup handler = obj[key]; url = prefix + url; // before middleware support if (obj.before) { app[method](url, obj.before, handler); verbose && console.log( " %s %s -> before -> %s", method.toUpperCase(), url, key ); } else { app[method](url, handler); verbose && console.log(" %s %s -> %s", method.toUpperCase(), url, key); } } // mount the app parent.use(app); }); }; ================================================ FILE: package.json ================================================ { "name": "bia", "version": "4.2.0", "description": "### Período do evento: 23/05 a 24/05/2026 (Online e ao vivo das 9h30 às 17h30)", "main": "index.js", "scripts": { "test": "jest tests/unit", "start": "node server", "start_db": "node config/database.js" }, "repository": { "type": "git", "url": "git+https://github.com/henrylle/bia.git" }, "author": "", "license": "ISC", "bugs": { "url": "https://github.com/henrylle/bia/issues" }, "homepage": "https://github.com/henrylle/bia#readme", "dependencies": { "@aws-sdk/client-secrets-manager": "^3.583.0", "@aws-sdk/client-sts": "^3.583.0", "@aws-sdk/credential-providers": "^3.583.0", "config": "^4.1.1", "cors": "^2.8.5", "ejs": "^3.1.6", "express": "^4.17.1", "express-session": "^1.17.2", "hbs": "^4.1.2", "json-server": "^0.17.4", "method-override": "^3.0.0", "morgan": "^1.10.0", "pg": "^8.7.1", "pg-hstore": "^2.3.4", "sequelize": "^6.6.5" }, "eslintConfig": { "extends": [ "react-app" ] }, "devDependencies": { "jest": "^27.5.1", "sequelize-cli": "^6.2.0" } } ================================================ FILE: rodar_app_local_unix.sh ================================================ docker compose up -d database npm install --loglevel=error npm run build --prefix client --loglevel=error npx sequelize db:migrate npm start ================================================ FILE: rodar_app_local_windows.bat ================================================ @echo off docker compose up -d database if %errorlevel% neq 0 exit /b %errorlevel% call npm install -g npm@latest --loglevel=error call npm install --loglevel=error call npm run build --prefix client --loglevel=error call npx sequelize db:migrate npm start ================================================ FILE: scripts/criar_role_ssm.sh ================================================ role_name="role-acesso-ssm" policy_name="AmazonSSMManagedInstanceCore" if aws iam get-role --role-name "$role_name" &> /dev/null; then echo "A IAM role $role_name já existe." exit 1 fi aws iam create-role --role-name $role_name --assume-role-policy-document file://ec2_principal.json # Cria o perfil de instância aws iam create-instance-profile --instance-profile-name $role_name # Adiciona a função IAM ao perfil de instância aws iam add-role-to-instance-profile --instance-profile-name $role_name --role-name $role_name aws iam attach-role-policy --role-name $role_name --policy-arn arn:aws:iam::aws:policy/$policy_name ================================================ FILE: scripts/ec2_principal.json ================================================ { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }] } ================================================ FILE: scripts/ecs/unix/build.sh ================================================ ECR_REGISTRY="SEU_REGISTRY" aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_REGISTRY docker build -t bia . docker tag bia:latest $ECR_REGISTRY/bia:latest docker push $ECR_REGISTRY/bia:latest ================================================ FILE: scripts/ecs/unix/check-disponibilidade.sh ================================================ url="https://www.uol.com.br" docker build -t check_disponibilidade -f Dockerfile_checkdisponibilidade . docker run --rm -ti -e URL=$url check_disponibilidade ================================================ FILE: scripts/ecs/unix/deploy.sh ================================================ ./build.sh aws ecs update-service --cluster [SEU_CLUSTER] --service [SEU_SERVICE] --force-new-deployment ================================================ FILE: scripts/ecs/unix/testar-latencia.sh ================================================ #URL="https://pages.formacaoaws.com.br/" URL="https://pages.formacaoaws.com.br/bootcamp-imersao-aws-ga-qt-fu/" #URL="https://pages.formacaoaws.com.br/wp-includes/images/blank.gif" while true; do START=$(date '+%Y-%m-%d %H:%M:%S') START_MS=$(gdate +%s%3N) # Captura todos os cabeçalhos HEADERS=$(curl -s -I -X GET $URL) # Extração dos valores desejados X_CACHE=$(echo "$HEADERS" | grep -i x-cache | awk -F ': ' '{print $2}' | tr -d '\r') CACHE_CONTROL=$(echo "$HEADERS" | grep -i cache-control | awk -F ': ' '{print $2}' | tr -d '\r') END_MS=$(gdate +%s%3N) DURATION=$((END_MS - START_MS)) # Adiciona o Cache-Control apenas se X-Cache contiver "Miss from cloudfront" if [[ "$X_CACHE" == *"Miss from cloudfront"* ]]; then X_CACHE="$X_CACHE ($CACHE_CONTROL)" fi # Exibe o log com os cabeçalhos desejados echo "$START - $DURATION ms - ${X_CACHE:-N/A}" sleep 2; done ================================================ FILE: scripts/ecs/windows/build.bat ================================================ aws ecr get-login-password --region us-east-1 --profile [SEU_PROFILE] | docker login --username AWS --password-stdin [SEU_ECR] docker build -t bia . docker tag bia:latest [SEU_ECR]/bia:latest docker push [SEU_ECR]/bia:latest ================================================ FILE: scripts/ecs/windows/check-disponibilidade.bat ================================================ set url="https://www.uol.com.br" docker build -t check_disponibilidade -f Dockerfile_checkdisponibilidade . docker run --rm -ti -e URL=%url% check_disponibilidade ================================================ FILE: scripts/ecs/windows/deploy.bat ================================================ call build.bat aws ecs update-service --cluster [SEU_CLUSTER] --service [SEU_SERVICE] --force-new-deployment --profile [SEU_PROFILE] ================================================ FILE: scripts/generate-sts-token.sh ================================================ #!/bin/bash # Script para gerar token STS com base no profile AWS # Uso: ./generate-sts-token.sh [duration-seconds] [--export] # Função para mostrar ajuda show_help() { echo "Uso: $0 [duration-seconds] [--export]" echo "" echo "Parâmetros:" echo " profile-name Nome do profile AWS (obrigatório)" echo " duration-seconds Duração em segundos (opcional, padrão: 3600)" echo " --export Exporta as credenciais na sessão atual (opcional)" echo "" echo "Exemplos:" echo " $0 meu-profile # Token de 1 hora" echo " $0 meu-profile 7200 # Token de 2 horas" echo " $0 meu-profile 3600 --export # Token de 1 hora + export automático" echo " $0 meu-profile --export # Token de 1 hora + export automático" echo " source <($0 meu-profile --export) # Alternativa para export" echo "" echo "Nota: Duração mínima é de 15 minutos (900 segundos)" echo " Duração máxima é de 12 horas (43200 segundos)" } # Inicializa variáveis PROFILE_NAME="" DURATION=3600 # Default: 1 hora EXPORT_MODE=false # Processa argumentos while [[ $# -gt 0 ]]; do case $1 in --export) EXPORT_MODE=true shift ;; --help|-h) show_help exit 0 ;; *) if [[ -z "$PROFILE_NAME" ]]; then PROFILE_NAME="$1" elif [[ "$1" =~ ^[0-9]+$ ]]; then DURATION="$1" else echo "Erro: Argumento inválido '$1'" show_help exit 1 fi shift ;; esac done # Verifica se profile foi informado if [[ -z "$PROFILE_NAME" ]]; then echo "Erro: Profile AWS não informado" echo "" show_help exit 1 fi # Valida duração mínima (15 minutos = 900 segundos) if [ "$DURATION" -lt 900 ]; then echo "Erro: Duração mínima é de 15 minutos (900 segundos)" echo "Duração informada: ${DURATION} segundos" echo "Use: $0 ${PROFILE_NAME} 900 (ou maior)" exit 1 fi # Verifica se o profile existe PROFILES=$(aws configure list-profiles 2>/dev/null) if ! echo "${PROFILES}" | grep -q "^${PROFILE_NAME}$"; then echo "Erro: Profile '${PROFILE_NAME}' não encontrado" echo "Profiles disponíveis:" echo "${PROFILES}" exit 1 fi if [ "$EXPORT_MODE" = false ]; then echo "Gerando token STS para o profile: ${PROFILE_NAME}" echo "Duração: ${DURATION} segundos" echo "----------------------------------------" fi # Gera o token STS STS_OUTPUT=$(aws sts get-session-token \ --profile "${PROFILE_NAME}" \ --duration-seconds "${DURATION}" \ --output json 2>/dev/null) if [ $? -ne 0 ]; then echo "Erro ao gerar token STS. Verifique suas credenciais e permissões." exit 1 fi # Extrai as credenciais do output ACCESS_KEY=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.AccessKeyId') SECRET_KEY=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.SecretAccessKey') SESSION_TOKEN=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.SessionToken') EXPIRATION=$(echo "${STS_OUTPUT}" | jq -r '.Credentials.Expiration') if [ "$EXPORT_MODE" = true ]; then # Modo export: só mostra os comandos export para serem executados via source echo "export AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "export AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "export AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "# Credenciais exportadas para a sessão atual!" echo "# Expira em: ${EXPIRATION}" else # Modo normal: mostra todas as informações echo "Token STS gerado com sucesso!" echo "Expira em: ${EXPIRATION}" echo "" echo "=== Credenciais temporárias ===" echo "AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Para usar no terminal ===" echo "export AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "export AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "export AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Para usar em arquivo .env ===" echo "AWS_ACCESS_KEY_ID=${ACCESS_KEY}" echo "AWS_SECRET_ACCESS_KEY=${SECRET_KEY}" echo "AWS_SESSION_TOKEN=${SESSION_TOKEN}" echo "" echo "=== Para exportar automaticamente ===" echo "source <($0 ${PROFILE_NAME} ${DURATION} --export)" echo "" echo "=== Para salvar em arquivo ===" echo "Para salvar as variáveis em um arquivo:" echo "$0 ${PROFILE_NAME} ${DURATION} > sts-credentials.env" fi ================================================ FILE: scripts/lancar_ec2_zona_a.sh ================================================ vpc_id=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query "Vpcs[0].VpcId" --output text) subnet_id=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$vpc_id Name=availabilityZone,Values=us-east-1a --query "Subnets[0].SubnetId" --output text) security_group_id=$(aws ec2 describe-security-groups --group-names "bia-dev" --query "SecurityGroups[0].GroupId" --output text 2>/dev/null) if [ -z "$security_group_id" ]; then echo ">[ERRO] Security group bia-dev não foi criado na VPC $vpc_id" exit 1 fi aws ec2 run-instances --image-id ami-02f3f602d23f1659d --count 1 --instance-type t3.micro \ --security-group-ids $security_group_id --subnet-id $subnet_id --associate-public-ip-address \ --block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"VolumeSize":15,"VolumeType":"gp2"}}]' \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bia-dev}]' \ --iam-instance-profile Name=role-acesso-ssm --user-data file://user_data_ec2_zona_a.sh ================================================ FILE: scripts/ligar_bia_local.sh ================================================ nome="bia-dev" instance_id=$(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --filters "Name=tag:Name,Values=$nome" --output text) aws ec2 start-instances --instance-ids $instance_id ================================================ FILE: scripts/parar_bia_local.sh ================================================ nome="bia-dev" instance_id=$(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --filters "Name=tag:Name,Values=$nome" --output text) aws ec2 stop-instances --instance-ids $instance_id ================================================ FILE: scripts/setup_bia_dev_ubuntu_ui.sh ================================================ #!/bin/bash sudo apt-get -y update # INSTALANDO DOCKER sudo apt-get install -y ca-certificates curl gnupg lsb-release curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get -y update sudo apt-get install -y docker-ce docker-ce-cli containerd.io # Startando e habilitando docker para já iniciar ativo sudo systemctl enable docker.service sudo systemctl enable containerd.service # Instalando docker-compose sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose # Instalando o Node sudo curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash - sudo apt-get install -y nodejs # Atualizando versao do NPM sudo npm install -g npm@latest --loglevel=error # Instalando AWS CLI # Pre-requisito (unzip) sudo apt-get install unzip -y # AWS CLI (Install) sudo curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" sudo unzip awscliv2.zip sudo ./aws/install # Configurando permissão no docker para não ter que ficar usando root sudo usermod -aG docker ubuntu newgrp docker ================================================ FILE: scripts/setup_cloudshell_ssm.sh ================================================ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" -o "session-manager-plugin.rpm" sudo yum install -y session-manager-plugin.rpm ================================================ FILE: scripts/setup_ui_ubuntu.sh ================================================ # INSTALANDO UI sudo apt update -y && \ sudo apt upgrade -y && \ sudo apt install xfce4 xfce4-goodies -y # INSTALANDO CHROME REMOTE DESKTOP wget https://dl.google.com/linux/direct/chrome-remote-desktop_current_amd64.deb && \ sudo apt install ./chrome-remote-desktop_current_amd64.deb -y # DANDO PERMISSÕES NA PASTA sudo chmod 777 /home/ubuntu/.config/ ================================================ FILE: scripts/setup_vscode+chrome.sh ================================================ #!/bin/bash #Instalando VS CODE sudo snap install --classic code #Instalando Chrome wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome-stable_current_amd64.deb ================================================ FILE: scripts/start-session-bash.sh ================================================ INSTANCE_ID=$1 echo "Conectando na instancia $INSTANCE_ID" aws ssm start-session --target $INSTANCE_ID --document-name AWS-StartInteractiveCommand --parameters command="bash -l" ================================================ FILE: scripts/user_data_ec2_zona_a.sh ================================================ #!/bin/bash #Instalar Docker, Git, jq e AWS CLI sudo yum update -y sudo yum install git -y sudo yum install docker -y sudo yum install jq -y #Instalar AWS CLI v2 curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" sudo yum install unzip -y unzip awscliv2.zip sudo ./aws/install rm -rf awscliv2.zip aws/ sudo usermod -a -G docker ec2-user sudo usermod -a -G docker ssm-user id ec2-user ssm-user sudo newgrp docker #Ativar docker sudo systemctl enable docker.service sudo systemctl start docker.service #Instalar docker compose 2 sudo mkdir -p /usr/local/lib/docker/cli-plugins sudo curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose #Adicionar swap sudo dd if=/dev/zero of=/swapfile bs=128M count=32 sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile sudo echo "/swapfile swap swap defaults 0 0" >> /etc/fstab #Instalar node e npm curl -fsSL https://rpm.nodesource.com/setup_21.x | sudo bash - sudo yum install -y nodejs #Configurar python 3.11 e uv para uso com mcp servers da aws sudo dnf install python3.11 -y sudo ln -sf /usr/bin/python3.11 /usr/bin/python3 sudo -u ec2-user bash -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/ec2-user/.bashrc ================================================ FILE: scripts/validar_recursos_zona_a.sh ================================================ vpc_id=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query "Vpcs[0].VpcId" --output text 2>/dev/null) if [ $? -eq 0 ]; then echo "[OK] Tudo certo com a VPC" else echo ">[ERRO] Tenho um problema ao retornar a VPC default. Será se ela existe?" fi subnet_id=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$vpc_id Name=availabilityZone,Values=us-east-1a --query "Subnets[0].SubnetId" --output text 2>/dev/null) if [ $? -eq 0 ]; then echo "[OK] Tudo certo com a Subnet" else echo ">[ERRO] Tenho um problema ao retornar a subnet da zona a. Será se existe uma subnet na zona A?" fi security_group_id=$(aws ec2 describe-security-groups --group-names "bia-dev" --query "SecurityGroups[0].GroupId" --output text 2>/dev/null) if [ $? -eq 0 ]; then echo "[OK] Security Group bia-dev foi criado" # Validar inbound rule para o security group 'bia-dev' inbound_rule=$(aws ec2 describe-security-groups --group-ids $security_group_id --filters "Name=ip-permission.from-port,Values=3001" --filters "Name=ip-permission.cidr,Values=0.0.0.0/0" --output text) if [ -n "$inbound_rule" ]; then echo " [OK] Regra de entrada está ok" else echo " >[ERRO] Regra de entrada para a porta 3001 não encontrada ou não está aberta para o mundo todo. Reveja a aula do Henrylle" fi # Validar outbound rule para o security group 'bia-dev' outobund_rule=$(aws ec2 describe-security-groups --group-ids $security_group_id --query "SecurityGroups[0].IpPermissionsEgress[?IpProtocol=='-1' && IpRanges[0].CidrIp=='0.0.0.0/0']" --output text) if [ -n "$outobund_rule" ]; then echo " [OK] Regra de saída está correta" else echo " >[ERRO] Regra de saída para o mundo não encontrada. Reveja a aula do Henrylle" fi else echo ">[ERRO] Não achei o security group bia-dev. Ele foi criado?" fi if aws iam get-role --role-name role-acesso-ssm &>/dev/null; then echo "[OK] Tudo certo com a role 'role-acesso-ssm'" else echo ">[ERRO] A role 'role-acesso-ssm' não existe" fi ================================================ FILE: scripts_evento/README.md ================================================ ## Nova Localização do Conteúdo O conteúdo desta seção foi movido para [**scripts/ecs**](https://github.com/henrylle/bia/tree/main/scripts/ecs). ================================================ FILE: server.js ================================================ const app = require("./config/express")(); const port = app.get("port"); // RODANDO NOSSA APLICAÇÃO NA PORTA SETADA app.listen(port, () => { console.log(`Servidor rodando na porta ${port}`); }); ================================================ FILE: tests/unit/controllers/tarefas.test.js ================================================ const tarefasController = require('../../../api/controllers/tarefas'); // Mock do initializeModels jest.mock('../../../api/models', () => { return jest.fn(); }); const initializeModels = require('../../../api/models'); describe('Tarefas Controller', () => { let req, res, mockTarefas; beforeEach(() => { req = { body: {}, params: {} }; res = { send: jest.fn(), status: jest.fn().mockReturnThis() }; mockTarefas = { create: jest.fn(), findByPk: jest.fn(), findAll: jest.fn(), destroy: jest.fn(), update: jest.fn() }; initializeModels.mockResolvedValue({ Tarefas: mockTarefas }); jest.clearAllMocks(); }); describe('create', () => { test('deve criar uma tarefa com sucesso', async () => { const novaTarefa = { titulo: 'Teste', dia_atividade: '2026-01-26', importante: true }; req.body = novaTarefa; const tarefaCriada = { uuid: '123', ...novaTarefa }; mockTarefas.create.mockResolvedValue(tarefaCriada); const { create } = tarefasController(); await create(req, res); expect(mockTarefas.create).toHaveBeenCalledWith(novaTarefa); expect(res.send).toHaveBeenCalledWith(tarefaCriada); }); test('deve retornar erro 500 ao falhar', async () => { req.body = { titulo: 'Teste' }; mockTarefas.create.mockRejectedValue(new Error('Erro no banco')); const { create } = tarefasController(); await create(req, res); expect(res.status).toHaveBeenCalledWith(500); expect(res.send).toHaveBeenCalledWith({ message: 'Erro no banco' }); }); }); describe('find', () => { test('deve retornar uma tarefa por uuid', async () => { const tarefa = { uuid: '123', titulo: 'Teste' }; req.params.uuid = '123'; mockTarefas.findByPk.mockResolvedValue(tarefa); const { find } = tarefasController(); await find(req, res); expect(mockTarefas.findByPk).toHaveBeenCalledWith('123'); expect(res.send).toHaveBeenCalledWith(tarefa); }); test('deve retornar 404 quando tarefa não existe', async () => { req.params.uuid = '999'; mockTarefas.findByPk.mockResolvedValue(null); const { find } = tarefasController(); await find(req, res); expect(res.status).toHaveBeenCalledWith(404); expect(res.send).toHaveBeenCalledWith({ message: 'Tarefa não encontrada.' }); }); test('deve retornar erro 500 ao falhar', async () => { req.params.uuid = '123'; mockTarefas.findByPk.mockRejectedValue(new Error('Erro no banco')); const { find } = tarefasController(); await find(req, res); expect(res.status).toHaveBeenCalledWith(500); expect(res.send).toHaveBeenCalledWith({ message: 'Erro no banco' }); }); }); describe('findAll', () => { test('deve retornar todas as tarefas', async () => { const tarefas = [ { uuid: '1', titulo: 'Tarefa 1' }, { uuid: '2', titulo: 'Tarefa 2' } ]; mockTarefas.findAll.mockResolvedValue(tarefas); const { findAll } = tarefasController(); await findAll(req, res); expect(mockTarefas.findAll).toHaveBeenCalled(); expect(res.send).toHaveBeenCalledWith(tarefas); }); test('deve retornar erro 500 ao falhar', async () => { mockTarefas.findAll.mockRejectedValue(new Error('Erro no banco')); const { findAll } = tarefasController(); await findAll(req, res); expect(res.status).toHaveBeenCalledWith(500); expect(res.send).toHaveBeenCalledWith({ message: 'Erro no banco' }); }); }); describe('delete', () => { test('deve deletar uma tarefa com sucesso', async () => { req.params.uuid = '123'; mockTarefas.destroy.mockResolvedValue(1); const { delete: deleteFn } = tarefasController(); await deleteFn(req, res); expect(mockTarefas.destroy).toHaveBeenCalledWith({ where: { uuid: '123' } }); expect(res.send).toHaveBeenCalledWith({ message: 'Tarefa deletada com sucesso.' }); }); test('deve retornar 404 quando tarefa não existe', async () => { req.params.uuid = '999'; mockTarefas.destroy.mockResolvedValue(0); const { delete: deleteFn } = tarefasController(); await deleteFn(req, res); expect(res.status).toHaveBeenCalledWith(404); expect(res.send).toHaveBeenCalledWith({ message: 'Tarefa não encontrada.' }); }); test('deve retornar erro 500 ao falhar', async () => { req.params.uuid = '123'; mockTarefas.destroy.mockRejectedValue(new Error('Erro no banco')); const { delete: deleteFn } = tarefasController(); await deleteFn(req, res); expect(res.status).toHaveBeenCalledWith(500); expect(res.send).toHaveBeenCalledWith({ message: 'Erro no banco' }); }); }); describe('update_priority', () => { test('deve atualizar uma tarefa com sucesso', async () => { const tarefaAtualizada = { uuid: '123', importante: true }; req.params.uuid = '123'; req.body = { importante: true }; mockTarefas.update.mockResolvedValue([1]); mockTarefas.findByPk.mockResolvedValue(tarefaAtualizada); const { update_priority } = tarefasController(); await update_priority(req, res); expect(mockTarefas.update).toHaveBeenCalledWith( { importante: true }, { where: { uuid: '123' } } ); expect(res.send).toHaveBeenCalledWith(tarefaAtualizada); }); test('deve retornar 404 quando tarefa não existe', async () => { req.params.uuid = '999'; req.body = { importante: true }; mockTarefas.update.mockResolvedValue([0]); mockTarefas.findByPk.mockResolvedValue(null); const { update_priority } = tarefasController(); await update_priority(req, res); expect(res.status).toHaveBeenCalledWith(404); expect(res.send).toHaveBeenCalledWith({ message: 'Tarefa não encontrada.' }); }); test('deve retornar erro 500 ao falhar', async () => { req.params.uuid = '123'; req.body = { importante: true }; mockTarefas.update.mockRejectedValue(new Error('Erro no banco')); const { update_priority } = tarefasController(); await update_priority(req, res); expect(res.status).toHaveBeenCalledWith(500); expect(res.send).toHaveBeenCalledWith({ message: 'Erro no banco' }); }); }); }); ================================================ FILE: tests/unit/controllers/versao.test.js ================================================ const versaoController = require('../../../api/controllers/versao'); describe('Versao Controller', () => { // Mock para simular o objeto req e res const req = {}; const res = { send: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); }); test('get deve retornar a string de resposta correta', () => { // Chama a função retornada pelo controller para obter o objeto controller const { get } = versaoController(); // Chama o método get do objeto controller get(req, res); expect(res.send).toHaveBeenCalledWith('Bia 4.2.0'); }); test('get deve retornar a string de resposta correta quando VERSAO_API não está definido', () => { // Simula o cenário onde VERSAO_API não está definido delete process.env.VERSAO_API; // Chama a função retornada pelo controller para obter o objeto controller const { get } = versaoController(); // Chama o método get do objeto controller get(req, res); expect(res.send).toHaveBeenCalledWith('Bia 4.2.0'); }); test('get deve retornar a string de resposta correta quando VERSAO_API está definido', () => { // Simula o cenário onde VERSAO_API está definido process.env.VERSAO_API = '1.0.0'; // Chama a função retornada pelo controller para obter o objeto controller const { get } = versaoController(); // Chama o método get do objeto controller get(req, res); expect(res.send).toHaveBeenCalledWith('Bia 1.0.0'); }); });