Full Code of henrylle/bia for AI

main 71b456307a63 cached
86 files
131.1 KB
36.7k tokens
8 symbols
1 requests
Download .txt
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": [
                "<node_internals>/**"
            ],
            "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": [
                "<node_internals>/**"
            ],
            "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
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon-simple.svg" type="image/svg+xml" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="/logo192.png" />
    <link rel="manifest" href="/manifest.json" />
    <title>BIA + IA - 2026</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>


================================================
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 = () => (
    <>
      <AddTask onAdd={addTask} />
      {tasks.length > 0 ? (
        <Tasks
          tasks={tasks}
          onDelete={deleteTask}
          onToggle={toggleReminder}
        />
      ) : (
        <div className="empty-state">
          <h3>Nenhuma tarefa por aqui 📝</h3>
          <p>Adicione sua primeira tarefa usando o formulário acima!</p>
        </div>
      )}
    </>
  );

  return (
    <div className="app">
      <Router>
        <div className="container">
          <Header />

          <Routes>
            <Route path="/" element={<HomePage />} />
            <Route path="/about" element={<About />} />
          </Routes>
          <Footer />
        </div>
        <DebugLogs />
      </Router>
    </div>
  );
}

function App() {
  return (
    <ThemeProvider>
      <LogProvider>
        <AppContent />
      </LogProvider>
    </ThemeProvider>
  );
}

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 (
    <div>
      <h4>Versão 4.2.0</h4>
      <h5>BIA 23/05 e 24/05/2026</h5>
      <Link to="/">Voltar</Link>
      <DadosHenrylle />
    </div>
  );
};

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 (
    <div className="about-page">
      <div className="about-content">
        <div className="feature-grid">
          <div className="feature-card highlight">
            <h3>Próximo Evento</h3>
            <h4>AWS & IA</h4>
            <p><strong>23/05 e 24/05/2026</strong><br/>Formação AWS</p>
          </div>
        </div>

        <DadosHenrylle />
      </div>

      <div className="about-footer">
        <Link to="/" className="back-button">
          ← Voltar
        </Link>
      </div>
    </div>
  );
};

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 (
    <form className="add-form" onSubmit={onSubmit}>
      <div className="form-control">
        <label>Tarefa</label>
        <input
          type="text"
          placeholder="O que você precisa fazer?"
          value={titulo}
          onChange={(e) => setTitulo(e.target.value)}
        />
      </div>
      
      <div className="form-control">
        <label>Data/Prazo</label>
        <input
          type="text"
          placeholder="Quando?"
          value={dia}
          onChange={(e) => setDia(e.target.value)}
        />
      </div>
      
      <div className="form-control-check">
        <input
          type="checkbox"
          id="importante"
          checked={importante}
          onChange={(e) => setImportante(e.target.checked)}
        />
        <label htmlFor="importante">Importante</label>
      </div>
      
      <button type="submit" className="btn btn-block success">
        Add New Task
      </button>
      
      <Modal
        isOpen={showModal}
        onClose={() => setShowModal(false)}
        title="Campo obrigatório"
        message="Por favor, adicione uma descrição para a tarefa"
        type="warning"
      />
    </form>
  );
};

export default AddTask;


================================================
FILE: client/src/components/Button.jsx
================================================
import React from "react";

const Button = ({ color, text, onClick }) => {
  return (
    <button
      onClick={onClick}
      className={`btn ${color ? color : ''}`}
    >
      {text}
    </button>
  );
};

Button.defaultProps = {
  color: "",
};

export default Button;


================================================
FILE: client/src/components/DadosHenrylle.jsx
================================================
import React from "react";

const DadosHenrylle = () => {
  return (
    <div className="dados-henrylle">
      <h3>Links Importantes</h3>
      <div className="links-grid">
        <a
          href="https://inscricao.formacaoaws.com.br/suporte"
          target="_blank"
          rel="noopener noreferrer"
          className="link-card"
        >
          <h4>💬 Suporte</h4>
          <p>Formação AWS</p>
        </a>
        
        <a
          href="https://instagram.com/henryllemaia"
          target="_blank"
          rel="noopener noreferrer"
          className="link-card"
        >
          <h4>📸 Instagram</h4>
          <p>Henrylle Maia</p>
        </a>
        
        <a
          href="https://www.youtube.com/@henryllemaia"
          target="_blank"
          rel="noopener noreferrer"
          className="link-card"
        >
          <h4>🎥 YouTube</h4>
          <p>Canal oficial</p>
        </a>
        
        <a
          href="https://www.linkedin.com/in/henrylle/recent-activity/all/"
          target="_blank"
          rel="noopener noreferrer"
          className="link-card"
        >
          <h4>💼 LinkedIn</h4>
          <p>Desafio Labs AWS</p>
        </a>
      </div>
    </div>
  );
};

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 (
    <div className="debug-logs">
      <div className="debug-header">
        <button 
          className="debug-toggle"
          onClick={toggleLogVisibility}
          title={isLogVisible ? "Ocultar logs" : "Mostrar logs"}
        >
          <FaBug />
          <span>Debug ({logs.length})</span>
          {isLogVisible ? <FaChevronUp /> : <FaChevronDown />}
        </button>
        
        {isLogVisible && (
          <button 
            className="debug-clear"
            onClick={clearLogs}
            title="Limpar logs"
          >
            <FaTrash />
          </button>
        )}
      </div>

      {isLogVisible && (
        <div className="debug-content">
          <div className="debug-info">
            <h4>🔧 Área de Debug</h4>
            <p>Esta área mostra logs da API para facilitar o debug durante o desenvolvimento.</p>
            <p><strong>URL da API:</strong> {import.meta.env.VITE_API_URL || "http://localhost:8080"}</p>
          </div>
          
          <div className="logs-container">
            {logs.length === 0 ? (
              <div className="no-logs">
                <p>Nenhum log ainda. Interaja com a aplicação para ver os logs aparecerem aqui.</p>
              </div>
            ) : (
              logs.map((log) => (
                <div key={log.id} className={`log-entry ${getLogClass(log.type)}`}>
                  <div className="log-header">
                    <span className="log-icon">{getLogIcon(log.type)}</span>
                    <span className="log-time">{log.timestamp}</span>
                    <span className="log-type">{log.type}</span>
                    <span className="log-message">{log.message}</span>
                  </div>
                  {log.details && (
                    <div className="log-details">
                      {log.details}
                    </div>
                  )}
                </div>
              ))
            )}
          </div>
        </div>
      )}
    </div>
  );
};

export default DebugLogs; 

================================================
FILE: client/src/components/Footer.jsx
================================================
import React from "react";
import { Link } from "react-router-dom";

const Footer = () => {
  return (
    <footer>
      <div className="footer-content">
        <p>Formação AWS 2026</p>
        <Link to="/about" className="footer-link">
          Sobre a BIA
        </Link>
      </div>
    </footer>
  );
};

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 (
    <header className="header">
      <h1>{title}</h1>
      <div className="header-controls">
        <VersionInfo />
        <button 
          className="theme-toggle" 
          onClick={toggleTheme}
          title={isDarkMode ? "Tema claro" : "Tema escuro"}
        >
          {isDarkMode ? <FaSun /> : <FaMoon />}
        </button>
      </div>
    </header>
  );
};

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 (
    <div className="modal-overlay" onClick={onClose}>
      <div className={`modal-content ${getTypeClass()}`} onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <span className="modal-icon">{getIcon()}</span>
          <h3 className="modal-title">{title || 'Atenção'}</h3>
        </div>
        <div className="modal-body">
          <p>{message}</p>
        </div>
        <div className="modal-footer">
          <button className="modal-btn" onClick={onClose}>
            OK
          </button>
        </div>
      </div>
    </div>
  );
};

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 (
    <div
      className={`task ${task.importante ? "reminder" : ""}`}
      onDoubleClick={() => onToggle(task.uuid)}
    >
      <div className="task-content">
        <h3>{task.titulo}</h3>
        <p className="task-date">
          📅 {task.dia_atividade || "Sem data definida"}
        </p>
      </div>
      <div className="task-actions">
        <button
          className="task-priority"
          onClick={() => onToggle(task.uuid)}
          title={task.importante ? "Remover importante" : "Marcar importante"}
        >
          {task.importante ? <FaStar /> : <FaRegStar />}
        </button>
        <button
          className="task-delete"
          onClick={() => onDelete(task.uuid)}
          title="Excluir"
        >
          <FaTimes />
        </button>
      </div>
    </div>
  );
};

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 (
    <div className="tasks-container">
      {/* Lista de tarefas da página atual */}
      <div className="tasks-list">
        {currentTasks.map((task) => (
          <Task
            key={task.uuid}
            task={task}
            onDelete={onDelete}
            onToggle={onToggle}
          />
        ))}
      </div>

      {/* Controles de paginação */}
      {totalPages > 1 && (
        <div className="pagination">
          <div className="pagination-info">
            <span>
              Mostrando {indexOfFirstTask + 1}-{Math.min(indexOfLastTask, tasks.length)} de {tasks.length} tarefas
            </span>
          </div>
          
          <div className="pagination-controls">
            <button 
              className="pagination-btn"
              onClick={goToPrevious}
              disabled={currentPage === 1}
              title="Página anterior"
            >
              ‹
            </button>
            
            {/* 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 (
                  <button
                    key={pageNumber}
                    className={`pagination-btn ${currentPage === pageNumber ? 'active' : ''}`}
                    onClick={() => goToPage(pageNumber)}
                  >
                    {pageNumber}
                  </button>
                );
              }
              
              // Mostrar reticências
              if (
                pageNumber === currentPage - 2 ||
                pageNumber === currentPage + 2
              ) {
                return <span key={pageNumber} className="pagination-dots">...</span>;
              }
              
              return null;
            })}
            
            <button 
              className="pagination-btn"
              onClick={goToNext}
              disabled={currentPage === totalPages}
              title="Próxima página"
            >
              ›
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

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 (
    <div className="version-info">
      <button 
        className={`version-trigger ${apiStatus} ${getEnvironmentInfo().type}`}
        onClick={handleVersionClick}
        title={`${getEnvironmentInfo().icon} ${getEnvironmentInfo().label} | API: ${getStatusText()}`}
        style={{
          borderColor: apiStatus === 'online' ? getEnvironmentInfo().color : 
                      apiStatus === 'offline' ? '#ef4444' : 
                      '#f59e0b'
        }}
      >
        {getStatusIcon()}
      </button>
             {showVersion && (
         <div className="version-tooltip">
           <div className="version-content">
             <strong>{apiVersion}</strong>
             <div className="version-details">
               <small>
                 <span className="status-indicator">{getStatusIcon()}</span>
                 Status: {getStatusText()}
               </small>
               <small>
                 <span 
                   className="env-indicator" 
                   style={{ color: getEnvironmentInfo().color }}
                 >
                   {getEnvironmentInfo().icon}
                 </span>
                 Ambiente: {getEnvironmentInfo().label}
               </small>
               <small>Local: {getEnvironmentInfo().description}</small>
               <small>API: {getApiUrl()}</small>
               <small>
                 <button 
                   className="version-link" 
                   onClick={openVersionEndpoint}
                   title="Abrir endpoint de versão"
                 >
                   🔗 /api/versao
                 </button>
               </small>
               <small>
                 <button 
                   className="version-link refresh-btn" 
                   onClick={checkApiHealth}
                   title="Verificar status da API"
                   disabled={apiStatus === 'checking'}
                 >
                   🔄 {apiStatus === 'checking' ? 'Verificando...' : 'Atualizar'}
                 </button>
               </small>
             </div>
           </div>
         </div>
       )}
    </div>
  );
};

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 (
    <LogContext.Provider value={{
      logs,
      isLogVisible,
      debugMode,
      addLog,
      clearLogs,
      toggleLogVisibility,
      logApiRequest,
      logApiResponse,
      logApiError,
    }}>
      {children}
    </LogContext.Provider>
  );
}; 

================================================
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 (
    <ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}; 

================================================
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(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// 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
================================================
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Arquitetura AWS - Projeto BIA com ECS/EC2</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #FF9500 0%, #FF6B35 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        
        .header h1 {
            margin: 0;
            font-size: 2.5em;
            font-weight: 300;
        }
        
        .header p {
            margin: 10px 0 0 0;
            opacity: 0.9;
            font-size: 1.1em;
        }
        
        .architecture-diagram {
            padding: 40px;
            background: #f8f9fa;
        }
        
        .aws-region {
            border: 3px dashed #FF9500;
            border-radius: 15px;
            padding: 30px;
            margin: 20px 0;
            background: white;
            position: relative;
        }
        
        .region-label {
            position: absolute;
            top: -15px;
            left: 20px;
            background: #FF9500;
            color: white;
            padding: 5px 15px;
            border-radius: 20px;
            font-weight: bold;
            font-size: 0.9em;
        }
        
        .vpc {
            border: 2px solid #4CAF50;
            border-radius: 10px;
            padding: 25px;
            margin: 20px 0;
            background: #f0f8f0;
            position: relative;
        }
        
        .vpc-label {
            position: absolute;
            top: -12px;
            left: 15px;
            background: #4CAF50;
            color: white;
            padding: 3px 12px;
            border-radius: 15px;
            font-size: 0.8em;
            font-weight: bold;
        }
        
        .subnet-row {
            display: flex;
            gap: 20px;
            margin: 20px 0;
            flex-wrap: wrap;
        }
        
        .subnet {
            flex: 1;
            min-width: 300px;
            border: 2px solid #2196F3;
            border-radius: 8px;
            padding: 20px;
            background: #e3f2fd;
            position: relative;
        }
        
        .subnet-label {
            position: absolute;
            top: -12px;
            left: 15px;
            background: #2196F3;
            color: white;
            padding: 3px 10px;
            border-radius: 12px;
            font-size: 0.75em;
            font-weight: bold;
        }
        
        .service-box {
            background: white;
            border-radius: 8px;
            padding: 15px;
            margin: 10px 0;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            border-left: 4px solid #FF6B35;
            transition: transform 0.2s;
        }
        
        .service-box:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0,0,0,0.15);
        }
        
        .service-title {
            font-weight: bold;
            color: #333;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .service-icon {
            width: 24px;
            height: 24px;
            background: #FF6B35;
            border-radius: 4px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 12px;
            font-weight: bold;
        }
        
        .service-description {
            color: #666;
            font-size: 0.9em;
            line-height: 1.4;
        }
        
        .external-services {
            display: flex;
            gap: 20px;
            margin: 30px 0;
            flex-wrap: wrap;
        }
        
        .external-service {
            flex: 1;
            min-width: 250px;
            background: #fff3e0;
            border: 2px solid #FF9500;
            border-radius: 8px;
            padding: 20px;
            text-align: center;
        }
        
        .flow-arrows {
            text-align: center;
            margin: 20px 0;
            color: #666;
            font-size: 1.2em;
        }
        
        .tech-stack {
            background: #f5f5f5;
            padding: 30px;
            margin-top: 20px;
        }
        
        .tech-stack h3 {
            color: #333;
            margin-bottom: 20px;
            text-align: center;
        }
        
        .tech-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
        }
        
        .tech-item {
            background: white;
            padding: 15px;
            border-radius: 8px;
            text-align: center;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        .tech-category {
            font-weight: bold;
            color: #FF6B35;
            margin-bottom: 8px;
        }
        
        .benefits {
            background: #e8f5e8;
            padding: 30px;
            margin-top: 20px;
        }
        
        .benefits h3 {
            color: #2e7d32;
            margin-bottom: 20px;
            text-align: center;
        }
        
        .benefits-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 15px;
        }
        
        .benefit-item {
            background: white;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #4CAF50;
        }
        
        .benefit-title {
            font-weight: bold;
            color: #2e7d32;
            margin-bottom: 5px;
        }
        
        @media (max-width: 768px) {
            .subnet-row {
                flex-direction: column;
            }
            
            .external-services {
                flex-direction: column;
            }
            
            .header h1 {
                font-size: 2em;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🏗️ Arquitetura AWS - Projeto BIA</h1>
            <p>ECS com EC2 + RDS PostgreSQL + CodePipeline</p>
        </div>
        
        <div class="architecture-diagram">
            <!-- Serviços Externos -->
            <div class="external-services">
                <div class="external-service">
                    <div class="service-title">
                        <div class="service-icon">👥</div>
                        Usuários
                    </div>
                    <div class="service-description">
                        Acesso via navegador web<br>
                        Interface React (Vite)
                    </div>
                </div>
                
                <div class="external-service">
                    <div class="service-title">
                        <div class="service-icon">🔧</div>
                        GitHub
                    </div>
                    <div class="service-description">
                        Repositório de código<br>
                        Trigger para CI/CD
                    </div>
                </div>
            </div>
            
            <div class="flow-arrows">⬇️ HTTPS / Git Webhook ⬇️</div>
            
            <!-- AWS Region -->
            <div class="aws-region">
                <div class="region-label">AWS Region (us-east-1)</div>
                
                <!-- Application Load Balancer -->
                <div class="service-box">
                    <div class="service-title">
                        <div class="service-icon">⚖️</div>
                        Application Load Balancer (ALB)
                    </div>
                    <div class="service-description">
                        • Distribuição de tráfego entre instâncias ECS<br>
                        • SSL/TLS termination<br>
                        • Health checks automáticos<br>
                        • Target Groups para containers
                    </div>
                </div>
                
                <div class="flow-arrows">⬇️ Distribui tráfego ⬇️</div>
                
                <!-- VPC -->
                <div class="vpc">
                    <div class="vpc-label">VPC (10.0.0.0/16)</div>
                    
                    <!-- Public Subnets -->
                    <div class="subnet-row">
                        <div class="subnet">
                            <div class="subnet-label">Public Subnet A (10.0.1.0/24)</div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🌐</div>
                                    NAT Gateway
                                </div>
                                <div class="service-description">
                                    Acesso à internet para subnets privadas
                                </div>
                            </div>
                        </div>
                        
                        <div class="subnet">
                            <div class="subnet-label">Public Subnet B (10.0.2.0/24)</div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🌐</div>
                                    NAT Gateway
                                </div>
                                <div class="service-description">
                                    Redundância para alta disponibilidade
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Private Subnets -->
                    <div class="subnet-row">
                        <div class="subnet">
                            <div class="subnet-label">Private Subnet A (10.0.3.0/24)</div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🐳</div>
                                    ECS Cluster + EC2 Instances
                                </div>
                                <div class="service-description">
                                    • Auto Scaling Group<br>
                                    • Instâncias t3.micro (2-4 instâncias)<br>
                                    • ECS Agent rodando<br>
                                    • Container BIA (Node.js + React)
                                </div>
                            </div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">📊</div>
                                    CloudWatch Logs
                                </div>
                                <div class="service-description">
                                    Logs da aplicação e containers
                                </div>
                            </div>
                        </div>
                        
                        <div class="subnet">
                            <div class="subnet-label">Private Subnet B (10.0.4.0/24)</div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🐳</div>
                                    ECS Cluster + EC2 Instances
                                </div>
                                <div class="service-description">
                                    • Réplica para alta disponibilidade<br>
                                    • Mesma configuração da Subnet A<br>
                                    • Load balancing automático
                                </div>
                            </div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🔐</div>
                                    Secrets Manager
                                </div>
                                <div class="service-description">
                                    Credenciais do banco de dados
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Database Subnets -->
                    <div class="subnet-row">
                        <div class="subnet">
                            <div class="subnet-label">DB Subnet A (10.0.5.0/24)</div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🗄️</div>
                                    RDS PostgreSQL (Primary)
                                </div>
                                <div class="service-description">
                                    • PostgreSQL 16<br>
                                    • db.t3.micro (para desenvolvimento)<br>
                                    • Automated backups<br>
                                    • Multi-AZ para produção
                                </div>
                            </div>
                        </div>
                        
                        <div class="subnet">
                            <div class="subnet-label">DB Subnet B (10.0.6.0/24)</div>
                            
                            <div class="service-box">
                                <div class="service-title">
                                    <div class="service-icon">🗄️</div>
                                    RDS PostgreSQL (Standby)
                                </div>
                                <div class="service-description">
                                    • Réplica síncrona (Multi-AZ)<br>
                                    • Failover automático<br>
                                    • Backup e recuperação
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                
                <!-- CI/CD Pipeline -->
                <div class="service-box" style="margin-top: 30px;">
                    <div class="service-title">
                        <div class="service-icon">🚀</div>
                        CI/CD Pipeline
                    </div>
                    <div class="service-description">
                        <strong>CodePipeline:</strong> GitHub → CodeBuild → ECR → ECS Deploy<br>
                        <strong>CodeBuild:</strong> Build da imagem Docker + Push para ECR<br>
                        <strong>ECR:</strong> Registry privado para imagens Docker<br>
                        <strong>ECS Service:</strong> Deploy automático com rolling updates
                    </div>
                </div>
            </div>
        </div>
        
        <!-- Tech Stack -->
        <div class="tech-stack">
            <h3>🛠️ Stack Tecnológica Identificada</h3>
            <div class="tech-grid">
                <div class="tech-item">
                    <div class="tech-category">Frontend</div>
                    React 18 + Vite<br>
                    React Router DOM<br>
                    React Icons
                </div>
                <div class="tech-item">
                    <div class="tech-category">Backend</div>
                    Node.js + Express<br>
                    Sequelize ORM<br>
                    Morgan (Logging)
                </div>
                <div class="tech-item">
                    <div class="tech-category">Database</div>
                    PostgreSQL 16<br>
                    Sequelize Migrations<br>
                    Connection Pooling
                </div>
                <div class="tech-item">
                    <div class="tech-category">AWS Services</div>
                    ECS + EC2<br>
                    RDS PostgreSQL<br>
                    Secrets Manager
                </div>
                <div class="tech-item">
                    <div class="tech-category">DevOps</div>
                    Docker<br>
                    CodePipeline<br>
                    CodeBuild + ECR
                </div>
                <div class="tech-item">
                    <div class="tech-category">Monitoramento</div>
                    CloudWatch Logs<br>
                    Health Checks<br>
                    Application Insights
                </div>
            </div>
        </div>
        
        <!-- Benefits -->
        <div class="benefits">
            <h3>✅ Benefícios desta Arquitetura</h3>
            <div class="benefits-grid">
                <div class="benefit-item">
                    <div class="benefit-title">Alta Disponibilidade</div>
                    Multi-AZ deployment com failover automático
                </div>
                <div class="benefit-item">
                    <div class="benefit-title">Escalabilidade</div>
                    Auto Scaling baseado em CPU/memória
                </div>
                <div class="benefit-item">
                    <div class="benefit-title">Segurança</div>
                    Subnets privadas + Secrets Manager
                </div>
                <div class="benefit-item">
                    <div class="benefit-title">CI/CD Automatizado</div>
                    Deploy automático via CodePipeline
                </div>
                <div class="benefit-item">
                    <div class="benefit-title">Monitoramento</div>
                    CloudWatch integrado + Health checks
                </div>
                <div class="benefit-item">
                    <div class="benefit-title">Custo Otimizado</div>
                    EC2 Spot instances + RDS otimizado
                </div>
            </div>
        </div>
    </div>
</body>
</html>


================================================
FILE: generate-sts-token.bat
================================================
@echo off
REM Script para Windows CMD que chama o script bash
REM Uso: generate-sts-token.bat <profile-name> [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 <profile-name> [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 <profile-name> [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
================================================
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        
        <title>Hello World Simple App</title>
    </head>
    <body>
        <div>Hello World!</div>
    </body>
</html>

================================================
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 <profile-name> [duration-seconds] [--export]

# Função para mostrar ajuda
show_help() {
    echo "Uso: $0 <profile-name> [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');
  });
});
Download .txt
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
Download .txt
SYMBOL INDEX (8 symbols across 2 files)

FILE: client/src/App.jsx
  function AppContent (line 14) | function AppContent() {
  function App (line 207) | function App() {

FILE: config/database.js
  function isLocalConnection (line 5) | async function isLocalConnection() {
  function getRemoteDialectOptions (line 15) | async function getRemoteDialectOptions() {
  function getConfig (line 25) | async function getConfig(){
  function createSecretsManagerClient (line 50) | async function createSecretsManagerClient() {
  function imprimirSecrets (line 87) | async function imprimirSecrets(secrets){
  function getSecrets (line 92) | async function getSecrets(secretsManagerClient) {
Condensed preview — 86 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (147K chars).
[
  {
    "path": ".dockerignore",
    "chars": 326,
    "preview": "node_modules\nnpm-debug.log\n.git\n.gitignore\nREADME.md\n.env\n.nyc_output\ncoverage\n.env.local\n.env.development.local\n.env.te"
  },
  {
    "path": ".gitignore",
    "chars": 395,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n\n\n#"
  },
  {
    "path": ".kiro/agents/bia.json",
    "chars": 856,
    "preview": "{\n    \"name\": \"bia\",\n    \"description\": \"Agente especialista em DevOps e Cloud AWS do projeto BIA da Formação AWS.\",\n   "
  },
  {
    "path": ".kiro/mcp-db.json",
    "chars": 440,
    "preview": "{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"-"
  },
  {
    "path": ".kiro/mcp-ecs.json",
    "chars": 413,
    "preview": "{\n  \"mcpServers\": {\n    \"awslabs.ecs-mcp-server\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"awslabs-ecs-mcp-se"
  },
  {
    "path": ".kiro/rules/dockerfile.md",
    "chars": 2433,
    "preview": "# Regras para Dockerfile - Projeto BIA\n\n## Filosofia de Desenvolvimento\n- **Público-alvo:** Alunos em aprendizado\n- **Ab"
  },
  {
    "path": ".kiro/rules/infraestrutura.md",
    "chars": 2625,
    "preview": "# Regras de Infraestrutura - Projeto BIA\n\n## Arquitetura Base\n- **Plataforma:** ECS com cluster de instâncias EC2\n- **Ev"
  },
  {
    "path": ".kiro/rules/pipeline.md",
    "chars": 2022,
    "preview": "# Regras de Pipeline - Projeto BIA\n\n## Definição de Pipeline\nSempre que falarmos de **pipeline** para este projeto, esta"
  },
  {
    "path": ".sequelizerc",
    "chars": 267,
    "preview": "const path = require('path');\n\nmodule.exports = {\n  'config': path.resolve('config', 'database.js'),\n  'models-path': pa"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1118,
    "preview": "{\n    \"version\": \"0.2.0\", \n    \"configurations\": [\n        {\n            \"name\": \"Testar DB com secrets\",\n            \"r"
  },
  {
    "path": "AmazonQ.md",
    "chars": 2712,
    "preview": "# Projeto BIA - Contexto e Análise\n\n## Visão Geral do Projeto\n**Nome:** BIA  \n**Versão:** 4.2.0  \n**Período da Imersão A"
  },
  {
    "path": "Dockerfile",
    "chars": 822,
    "preview": "FROM public.ecr.aws/docker/library/node:22.22.1-slim\nRUN npm install -g npm@11 --loglevel=error\n\n# Instalando curl\nRUN a"
  },
  {
    "path": "Dockerfile_checkdisponibilidade",
    "chars": 485,
    "preview": "FROM alpine\n\n# Define o fuso horário como São Paulo\nENV TZ=America/Sao_Paulo\n\n# Endereço para teste\nENV URL=https://www."
  },
  {
    "path": "README.md",
    "chars": 347,
    "preview": "## Projeto base para o evento Imersão AWS & IA que irei realizar.\n\n### Período do evento: 23/05 e 24/05/2026 (Online e a"
  },
  {
    "path": "api/controllers/tarefas.js",
    "chars": 2434,
    "preview": "const initializeModels = require(\"../models\");\n\nmodule.exports = () => {\n  const controller = {};\n\n  controller.create ="
  },
  {
    "path": "api/controllers/versao.js",
    "chars": 223,
    "preview": "module.exports = () => {\n  const controller = {};\n\n  controller.get = async (req, res) => {\n    const responseString = `"
  },
  {
    "path": "api/data/tarefas.json",
    "chars": 343,
    "preview": "{\n  \"tasks\": [{\n      \"id\": 1,\n      \"title\": \"Doctr Appoiment\",\n      \"day\": \"Feb 5th at 2:30pm\",\n      \"reminder\": tru"
  },
  {
    "path": "api/models/index.js",
    "chars": 1008,
    "preview": "\"use strict\";\n\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst Sequelize = require(\"sequelize\");\nconst bas"
  },
  {
    "path": "api/models/tarefas.js",
    "chars": 332,
    "preview": "module.exports = (sequelize, DataTypes) => {\n  const Tarefas = sequelize.define(\"Tarefas\", {\n    uuid: {\n      type: Dat"
  },
  {
    "path": "api/routes/ping.js",
    "chars": 123,
    "preview": "module.exports = (app) => {\n  app.route(\"/api/ping\").get((req, res) => {\n    res.json(\"Rota funcionando. Pong!\");\n  });\n"
  },
  {
    "path": "api/routes/tarefas.js",
    "chars": 1042,
    "preview": "module.exports = (app) => {\n  const controllerFactory = require(\"../controllers/tarefas\");\n  const controller = controll"
  },
  {
    "path": "api/routes/versao.js",
    "chars": 137,
    "preview": "module.exports = (app) => {\n  const controller = require(\"../controllers/versao\")();\n\n  app.route(\"/api/versao\").get(con"
  },
  {
    "path": "buildspec.yml",
    "chars": 1058,
    "preview": "version: 0.2\n\nphases:\n  pre_build:\n    commands:\n      - echo Fazendo login no ECR...      \n      - aws ecr get-login-pa"
  },
  {
    "path": "client/db.json",
    "chars": 349,
    "preview": "{\n  \"tasks\": [\n    {\n      \"id\": 1,\n      \"title\": \"Doctor Appoiment\",\n      \"day\": \"Feb 5th at 2:30pm\",\n      \"reminder"
  },
  {
    "path": "client/index.html",
    "chars": 742,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"/favicon-simple.svg\" t"
  },
  {
    "path": "client/package.json",
    "chars": 990,
    "preview": "{\n  \"name\": \"react-task-tracker\",\n  \"version\": \"0.1.0\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"dependencies\": {\n    \""
  },
  {
    "path": "client/public/manifest.json",
    "chars": 498,
    "preview": "{\n  \"short_name\": \"BIA\",\n  \"name\": \"BIA - Bot Inteligente de Atividades\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\","
  },
  {
    "path": "client/public/robots.txt",
    "chars": 67,
    "preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "client/src/App.jsx",
    "chars": 5645,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { BrowserRouter as Router, Routes, Route } from \"react-router"
  },
  {
    "path": "client/src/components/About.js",
    "chars": 321,
    "preview": "import React from \"react\";\nimport { Link } from \"react-router-dom\";\nimport DadosHenrylle from \"./DadosHenrylle\";\nconst A"
  },
  {
    "path": "client/src/components/About.jsx",
    "chars": 696,
    "preview": "import React from \"react\";\nimport { Link } from \"react-router-dom\";\nimport DadosHenrylle from \"./DadosHenrylle.jsx\";\n\nco"
  },
  {
    "path": "client/src/components/AddTask.jsx",
    "chars": 1853,
    "preview": "import React, { useState } from \"react\";\nimport Modal from \"./Modal\";\n\nconst AddTask = ({ onAdd }) => {\n  const [titulo,"
  },
  {
    "path": "client/src/components/Button.jsx",
    "chars": 274,
    "preview": "import React from \"react\";\n\nconst Button = ({ color, text, onClick }) => {\n  return (\n    <button\n      onClick={onClick"
  },
  {
    "path": "client/src/components/DadosHenrylle.jsx",
    "chars": 1265,
    "preview": "import React from \"react\";\n\nconst DadosHenrylle = () => {\n  return (\n    <div className=\"dados-henrylle\">\n      <h3>Link"
  },
  {
    "path": "client/src/components/DebugLogs.jsx",
    "chars": 2777,
    "preview": "import React from 'react';\nimport { FaBug, FaTimes, FaTrash, FaChevronDown, FaChevronUp } from 'react-icons/fa';\nimport "
  },
  {
    "path": "client/src/components/Footer.jsx",
    "chars": 336,
    "preview": "import React from \"react\";\nimport { Link } from \"react-router-dom\";\n\nconst Footer = () => {\n  return (\n    <footer>\n    "
  },
  {
    "path": "client/src/components/Header.jsx",
    "chars": 718,
    "preview": "import React from \"react\";\nimport { FaSun, FaMoon } from \"react-icons/fa\";\nimport { useTheme } from \"../contexts/ThemeCo"
  },
  {
    "path": "client/src/components/Modal.jsx",
    "chars": 1232,
    "preview": "import React from 'react';\n\nconst Modal = ({ isOpen, onClose, title, message, type = 'info' }) => {\n  if (!isOpen) retur"
  },
  {
    "path": "client/src/components/Task.jsx",
    "chars": 983,
    "preview": "import React from \"react\";\nimport { FaTimes, FaStar, FaRegStar } from \"react-icons/fa\";\n\nconst Task = ({ task, onDelete,"
  },
  {
    "path": "client/src/components/Tasks.jsx",
    "chars": 3619,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport Task from \"./Task.jsx\";\n\nconst Tasks = ({ tasks, onDelete, on"
  },
  {
    "path": "client/src/components/VersionInfo.jsx",
    "chars": 5982,
    "preview": "import React, { useState, useEffect } from 'react';\n\nconst VersionInfo = () => {\n  const [showVersion, setShowVersion] ="
  },
  {
    "path": "client/src/contexts/LogContext.jsx",
    "chars": 2574,
    "preview": "import React, { createContext, useContext, useState, useEffect } from 'react';\n\nconst LogContext = createContext();\n\nexp"
  },
  {
    "path": "client/src/contexts/ThemeContext.jsx",
    "chars": 997,
    "preview": "import React, { createContext, useContext, useState, useEffect } from 'react';\n\nconst ThemeContext = createContext();\n\ne"
  },
  {
    "path": "client/src/index.css",
    "chars": 15028,
    "preview": "@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');\n\n/* Variáveis de tema */\n:r"
  },
  {
    "path": "client/src/main.jsx",
    "chars": 566,
    "preview": "import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport './index.css';\nimport App from './App.j"
  },
  {
    "path": "client/src/reportWebVitals.js",
    "chars": 362,
    "preview": "const reportWebVitals = onPerfEntry => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals'"
  },
  {
    "path": "client/vite.config.js",
    "chars": 283,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n  plugins:"
  },
  {
    "path": "compose.yml",
    "chars": 884,
    "preview": "services:\n  server:\n    build: .\n    container_name: bia\n    ports:\n      - 3001:8080\n    depends_on:\n      - database\n "
  },
  {
    "path": "config/database.js",
    "chars": 3379,
    "preview": "const { SecretsManagerClient, GetSecretValueCommand } = require(\"@aws-sdk/client-secrets-manager\");\nconst { fromIni, fro"
  },
  {
    "path": "config/default.json",
    "chars": 38,
    "preview": "{\n  \"server\": {\n    \"port\": 8080\n  }\n}"
  },
  {
    "path": "config/express.js",
    "chars": 851,
    "preview": "const express = require(\"express\");\nvar cors = require(\"cors\");\nvar path = require(\"path\");\nconst config = require(\"conf"
  },
  {
    "path": "database/migrations/20210924000838-criar-tarefas.js",
    "chars": 847,
    "preview": "\"use strict\";\n\nmodule.exports = {\n  up: async (queryInterface, Sequelize) => {\n    return queryInterface.createTable(\"Ta"
  },
  {
    "path": "docs/README.md",
    "chars": 203,
    "preview": "# 📚 Documentação\n\n## 🏗️ Arquitetura AWS\n- [Diagrama ECS + EC2](./architecture/aws-ecs-diagram.html) - Visualização de um"
  },
  {
    "path": "docs/architecture/aws-ecs-diagram.html",
    "chars": 19028,
    "preview": "<!DOCTYPE html>\n<html lang=\"pt-BR\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-wi"
  },
  {
    "path": "generate-sts-token.bat",
    "chars": 577,
    "preview": "@echo off\nREM Script para Windows CMD que chama o script bash\nREM Uso: generate-sts-token.bat <profile-name> [duration-s"
  },
  {
    "path": "generate-sts-token.sh",
    "chars": 9107,
    "preview": "#!/bin/bash\n\n# Script universal para gerar token STS com base no profile AWS\n# Funciona em: Windows (Git Bash/WSL), macO"
  },
  {
    "path": "index.html",
    "chars": 283,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width="
  },
  {
    "path": "index.js",
    "chars": 2032,
    "preview": "var express = require(\"express\");\nvar logger = require(\"morgan\");\nvar path = require(\"path\");\nvar session = require(\"exp"
  },
  {
    "path": "lib/boot.js",
    "chars": 2355,
    "preview": "/**\n * Module dependencies.\n */\n\nvar express = require(\"express\");\nvar fs = require(\"fs\");\nvar path = require(\"path\");\n\n"
  },
  {
    "path": "package.json",
    "chars": 1160,
    "preview": "{\n  \"name\": \"bia\",\n  \"version\": \"4.2.0\",\n  \"description\": \"### Período do evento: 23/05 a 24/05/2026 (Online e ao vivo d"
  },
  {
    "path": "rodar_app_local_unix.sh",
    "chars": 140,
    "preview": "docker compose up -d database\nnpm install --loglevel=error\nnpm run build --prefix client --loglevel=error\nnpx sequelize "
  },
  {
    "path": "rodar_app_local_windows.bat",
    "chars": 257,
    "preview": "@echo off\ndocker compose up -d database\nif %errorlevel% neq 0 exit /b %errorlevel%\ncall npm install -g npm@latest --logl"
  },
  {
    "path": "scripts/criar_role_ssm.sh",
    "chars": 633,
    "preview": "role_name=\"role-acesso-ssm\"\npolicy_name=\"AmazonSSMManagedInstanceCore\"\n\nif aws iam get-role --role-name \"$role_name\" &> "
  },
  {
    "path": "scripts/ec2_principal.json",
    "chars": 170,
    "preview": "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [{\n    \"Effect\": \"Allow\",\n    \"Principal\": {\n      \"Service\": \"ec2.amazonaws"
  },
  {
    "path": "scripts/ecs/unix/build.sh",
    "chars": 241,
    "preview": "ECR_REGISTRY=\"SEU_REGISTRY\"\naws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin"
  },
  {
    "path": "scripts/ecs/unix/check-disponibilidade.sh",
    "chars": 158,
    "preview": "url=\"https://www.uol.com.br\"\ndocker build -t check_disponibilidade -f Dockerfile_checkdisponibilidade .\ndocker run --rm "
  },
  {
    "path": "scripts/ecs/unix/deploy.sh",
    "chars": 105,
    "preview": "./build.sh\naws ecs update-service --cluster [SEU_CLUSTER] --service [SEU_SERVICE]  --force-new-deployment"
  },
  {
    "path": "scripts/ecs/unix/testar-latencia.sh",
    "chars": 915,
    "preview": "#URL=\"https://pages.formacaoaws.com.br/\"\nURL=\"https://pages.formacaoaws.com.br/bootcamp-imersao-aws-ga-qt-fu/\"\n#URL=\"htt"
  },
  {
    "path": "scripts/ecs/windows/build.bat",
    "chars": 224,
    "preview": "aws ecr get-login-password --region us-east-1 --profile [SEU_PROFILE] | docker login --username AWS --password-stdin [SE"
  },
  {
    "path": "scripts/ecs/windows/check-disponibilidade.bat",
    "chars": 163,
    "preview": "set url=\"https://www.uol.com.br\"\ndocker build -t check_disponibilidade -f Dockerfile_checkdisponibilidade .\ndocker run -"
  },
  {
    "path": "scripts/ecs/windows/deploy.bat",
    "chars": 134,
    "preview": "call build.bat\naws ecs update-service --cluster [SEU_CLUSTER] --service [SEU_SERVICE]  --force-new-deployment --profile "
  },
  {
    "path": "scripts/generate-sts-token.sh",
    "chars": 4582,
    "preview": "#!/bin/bash\n\n# Script para gerar token STS com base no profile AWS\n# Uso: ./generate-sts-token.sh <profile-name> [durati"
  },
  {
    "path": "scripts/lancar_ec2_zona_a.sh",
    "chars": 984,
    "preview": "vpc_id=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query \"Vpcs[0].VpcId\" --output text)\nsubnet_id=$(a"
  },
  {
    "path": "scripts/ligar_bia_local.sh",
    "chars": 211,
    "preview": "nome=\"bia-dev\"\ninstance_id=$(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --filters \"Name="
  },
  {
    "path": "scripts/parar_bia_local.sh",
    "chars": 210,
    "preview": "nome=\"bia-dev\"\ninstance_id=$(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --filters \"Name="
  },
  {
    "path": "scripts/setup_bia_dev_ubuntu_ui.sh",
    "chars": 1492,
    "preview": "#!/bin/bash\nsudo apt-get -y update\n\n# INSTALANDO DOCKER\nsudo apt-get install -y ca-certificates curl gnupg lsb-release\nc"
  },
  {
    "path": "scripts/setup_cloudshell_ssm.sh",
    "chars": 189,
    "preview": "curl \"https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm\" -o \"sessi"
  },
  {
    "path": "scripts/setup_ui_ubuntu.sh",
    "chars": 360,
    "preview": "# INSTALANDO UI\nsudo apt update -y && \\\nsudo apt upgrade -y && \\\nsudo apt install xfce4 xfce4-goodies -y \n\n# INSTALANDO "
  },
  {
    "path": "scripts/setup_vscode+chrome.sh",
    "chars": 216,
    "preview": "#!/bin/bash\n\n#Instalando VS CODE\nsudo snap install --classic code\n\n#Instalando Chrome\nwget https://dl.google.com/linux/d"
  },
  {
    "path": "scripts/start-session-bash.sh",
    "chars": 177,
    "preview": "INSTANCE_ID=$1\necho \"Conectando na instancia $INSTANCE_ID\"\naws ssm start-session --target $INSTANCE_ID --document-name A"
  },
  {
    "path": "scripts/user_data_ec2_zona_a.sh",
    "chars": 1425,
    "preview": "#!/bin/bash\n\n#Instalar Docker, Git, jq e AWS CLI\nsudo yum update -y\nsudo yum install git -y\nsudo yum install docker -y\ns"
  },
  {
    "path": "scripts/validar_recursos_zona_a.sh",
    "chars": 2012,
    "preview": "vpc_id=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query \"Vpcs[0].VpcId\" --output text 2>/dev/null)\ni"
  },
  {
    "path": "scripts_evento/README.md",
    "chars": 146,
    "preview": "## Nova Localização do Conteúdo\n\nO conteúdo desta seção foi movido para [**scripts/ecs**](https://github.com/henrylle/bi"
  },
  {
    "path": "server.js",
    "chars": 199,
    "preview": "const app = require(\"./config/express\")();\nconst port = app.get(\"port\");\n\n// RODANDO NOSSA APLICAÇÃO NA PORTA SETADA\n\nap"
  },
  {
    "path": "tests/unit/controllers/tarefas.test.js",
    "chars": 6586,
    "preview": "const tarefasController = require('../../../api/controllers/tarefas');\n\n// Mock do initializeModels\njest.mock('../../../"
  },
  {
    "path": "tests/unit/controllers/versao.test.js",
    "chars": 1464,
    "preview": "const versaoController = require('../../../api/controllers/versao');\n\ndescribe('Versao Controller', () => {\n  // Mock pa"
  }
]

About this extraction

This page contains the full source code of the henrylle/bia GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 86 files (131.1 KB), approximately 36.7k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!