Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 335 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
name: CI — Mães em Ação

on:
push:
branches: [versao_1, main]
pull_request:
branches: [versao_1, main]

env:
NODE_VERSION: "20"

jobs:
# ────────────────────────────────────────────────────────────────
# JOB 1 — Lint (Backend + Frontend em paralelo)
# ────────────────────────────────────────────────────────────────
lint-backend:
name: "Lint · Backend"
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: backend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Run ESLint
run: npm run lint

lint-frontend:
name: "Lint · Frontend"
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Run ESLint
run: npm run lint

# ────────────────────────────────────────────────────────────────
# JOB 2 — Testes (Backend + Frontend em paralelo)
# ────────────────────────────────────────────────────────────────
test-backend:
name: "Testes · Backend (Jest)"
runs-on: ubuntu-latest
needs: lint-backend
defaults:
run:
working-directory: backend

env:
NODE_ENV: test
JWT_SECRET: "ci_test_secret_key_at_least_32_chars_long!!"
API_KEY_SERVIDORES: "ci_test_api_key_64_chars_for_scanner_balcao_valid_1234567890abcdef"
GEMINI_API_KEY: "dummy_gemini_key_for_ci"
GROQ_API_KEY: "gsk_dummy_groq_key_for_ci_testing_purposes"
DATABASE_URL: "postgresql://ci:ci@localhost:5432/ci_test"
DIRECT_URL: "postgresql://ci:ci@localhost:5432/ci_test"
SUPABASE_URL: "https://dummy.supabase.co"
SUPABASE_SERVICE_KEY: "dummy_service_key_for_ci"
QSTASH_TOKEN: "dummy_qstash_token"
QSTASH_CURRENT_SIGNING_KEY: "dummy_signing_key"
QSTASH_NEXT_SIGNING_KEY: "dummy_next_signing_key"
SALARIO_MINIMO_ATUAL: "1621.00"

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: backend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Generate Prisma Client
run: npx prisma generate

- name: Run Jest tests with coverage
run: npm test -- --forceExit

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: backend-coverage
path: backend/logs/coverage/
retention-days: 7

- name: Coverage summary
if: always()
run: |
echo "## Backend Coverage" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat logs/coverage/coverage-summary.json 2>/dev/null | node -e "
const data = require('fs').readFileSync('/dev/stdin','utf8');
try {
const j = JSON.parse(data).total;
console.log('Statements:', j.statements.pct + '%');
console.log('Branches: ', j.branches.pct + '%');
console.log('Functions: ', j.functions.pct + '%');
console.log('Lines: ', j.lines.pct + '%');
} catch { console.log('Coverage data not available'); }
" 2>/dev/null || echo "Coverage data not available"
Comment on lines +115 to +129
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Os resumos de cobertura nunca vão ler percentuais reais.

Hoje esses steps procuram coverage-summary.json, mas nem backend/jest.config.js nem frontend/vitest.config.js geram esse arquivo — ambos só exportam text, lcov e html. Na prática, o summary sempre cai em Coverage data not available, mesmo com cobertura gerada. Alinhe o workflow com um artefato existente ou adicione json-summary nos dois configs.

💡 Ajuste sugerido nos configs de teste
# backend/jest.config.js
-  coverageReporters: ["text", "lcov", "html"],
+  coverageReporters: ["text", "lcov", "html", "json-summary"],
# frontend/vitest.config.js
-      reporter: ["text", "lcov", "html"],
+      reporter: ["text", "lcov", "html", "json-summary"],

Also applies to: 166-180

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 115 - 129, The CI step currently
expects coverage-summary.json but the test configs (jest.config.js and
vitest.config.js) don't produce it; update both test configurations to include
the JSON summary reporter (json-summary / json) in their coverage reporters so a
coverage-summary.json is emitted, or alternatively change the CI step to read
whatever existing artifact those configs already produce (e.g., lcov or the
existing JSON path) and point cat to that file; specifically modify the
coverageReporters/coverageReporter settings in the jest.config.js and
vitest.config.js entries referenced in the diff and/or adjust the CI's cat path
to the actual file emitted so the node JSON-parsing block can read a real
coverage-summary.json.

echo '```' >> $GITHUB_STEP_SUMMARY

test-frontend:
name: "Testes · Frontend (Vitest)"
runs-on: ubuntu-latest
needs: lint-frontend
defaults:
run:
working-directory: frontend

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Run Vitest tests with coverage
run: npm run test:coverage
env:
VITE_API_URL: "http://localhost:8000"

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: frontend-coverage
path: frontend/coverage/
retention-days: 7

- name: Coverage summary
if: always()
run: |
echo "## Frontend Coverage" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat coverage/coverage-summary.json 2>/dev/null | node -e "
const data = require('fs').readFileSync('/dev/stdin','utf8');
try {
const j = JSON.parse(data).total;
console.log('Statements:', j.statements.pct + '%');
console.log('Branches: ', j.branches.pct + '%');
console.log('Functions: ', j.functions.pct + '%');
console.log('Lines: ', j.lines.pct + '%');
} catch { console.log('Coverage data not available'); }
" 2>/dev/null || echo "Coverage data not available"
echo '```' >> $GITHUB_STEP_SUMMARY

# ────────────────────────────────────────────────────────────────
# JOB 3 — Security Audit (npm audit --audit-level=high)
# ────────────────────────────────────────────────────────────────
security-audit:
name: "Segurança · npm audit"
runs-on: ubuntu-latest
# Roda em paralelo com testes — não bloqueia o pipeline por lint
needs: [lint-backend, lint-frontend]

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Audit Backend
working-directory: backend
run: |
npm ci
npm audit --audit-level=high --json > audit-backend.json || true
CRITICAL=$(node -e "
const a = require('./audit-backend.json');
const v = a.metadata?.vulnerabilities || {};
console.log(v.critical || 0);
")
HIGH=$(node -e "
const a = require('./audit-backend.json');
const v = a.metadata?.vulnerabilities || {};
console.log(v.high || 0);
")
echo "Backend — Critical: $CRITICAL | High: $HIGH"
echo "### Backend Audit" >> $GITHUB_STEP_SUMMARY
echo "- 🔴 Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY
echo "- 🟠 High: $HIGH" >> $GITHUB_STEP_SUMMARY
if [ "$CRITICAL" -gt "0" ]; then
echo "❌ Vulnerabilidades críticas encontradas no backend!" >&2
exit 1
fi

- name: Audit Frontend
working-directory: frontend
run: |
npm ci
npm audit --audit-level=high --json > audit-frontend.json || true
CRITICAL=$(node -e "
const a = require('./audit-frontend.json');
const v = a.metadata?.vulnerabilities || {};
console.log(v.critical || 0);
")
HIGH=$(node -e "
const a = require('./audit-frontend.json');
const v = a.metadata?.vulnerabilities || {};
console.log(v.high || 0);
")
echo "Frontend — Critical: $CRITICAL | High: $HIGH"
echo "### Frontend Audit" >> $GITHUB_STEP_SUMMARY
echo "- 🔴 Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY
echo "- 🟠 High: $HIGH" >> $GITHUB_STEP_SUMMARY
if [ "$CRITICAL" -gt "0" ]; then
echo "❌ Vulnerabilidades críticas encontradas no frontend!" >&2
exit 1
fi

# ────────────────────────────────────────────────────────────────
# JOB 4 — Build (valida que o código compila corretamente)
# ────────────────────────────────────────────────────────────────
build-backend:
name: "Build · Backend (prisma generate)"
runs-on: ubuntu-latest
needs: test-backend
defaults:
run:
working-directory: backend

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: backend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Generate Prisma Client
run: npx prisma generate

- name: Verify server.js imports (syntax check)
run: node --check server.js

build-frontend:
name: "Build · Frontend (vite build)"
runs-on: ubuntu-latest
needs: test-frontend
defaults:
run:
working-directory: frontend

env:
VITE_API_URL: "https://api.mutirao.dpe.ba.gov.br"

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: frontend-dist
path: frontend/dist/
retention-days: 3

# ────────────────────────────────────────────────────────────────
# JOB 5 — Status final (gate para merge)
# ────────────────────────────────────────────────────────────────
ci-success:
name: "✅ CI Completo"
runs-on: ubuntu-latest
needs: [build-backend, build-frontend, security-audit]
if: always()

steps:
- name: Verificar se todos os jobs passaram
run: |
if [[ "${{ needs.build-backend.result }}" != "success" ]] || \
[[ "${{ needs.build-frontend.result }}" != "success" ]] || \
[[ "${{ needs.security-audit.result }}" != "success" ]]; then
echo "❌ Um ou mais jobs falharam"
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY
echo "| build-backend | ${{ needs.build-backend.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| build-frontend | ${{ needs.build-frontend.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| security-audit | ${{ needs.security-audit.result }} |" >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "✅ Todos os jobs passaram com sucesso!" >> $GITHUB_STEP_SUMMARY
31 changes: 17 additions & 14 deletions arquivos/Conhecimento/01-Referencia/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Arquitetura do Sistema — Mães em Ação · DPE-BA

> **Versão:** 3.2 · **Atualizado em:** 2026-04-24 (CodeRabbit Audit + Dashboard v3.0 + CPF Mandatory)
> **Versão:** 4.2 · **Atualizado em:** 2026-04-26 (Hardening de Segurança + Assistência Compartilhada)
> **Contexto:** Mutirão estadual da Defensoria Pública da Bahia

---
Expand Down Expand Up @@ -180,12 +180,12 @@ stateDiagram-v2

### Locking — Sessões e Concorrência

- **Nível 1 (Servidor):** Bloqueia edição de dados jurídicos e relato
- **Nível 2 (Defensor):** Bloqueia a etapa de protocolo e finalização
- **HTTP 423 (Locked):** Retorno padrão quando outro usuário detém o lock
- **Admin Bypass:** Administradores podem forçar destravamento via painel
- **Nível 1 (Servidor/Estagiário/Defensor/Coordenador):** Atribuição de `servidor_id` — bloqueia edição de dados jurídicos e relato. Ativo em `pronto_para_analise` e `em_atendimento`.
- **Nível 2 (Defensor/Coordenador/Admin):** Atribuição de `defensor_id` — bloqueia etapa de protocolo e finalização. Ativo em `liberado_para_protocolo` e `em_protocolo`. **`servidor` e `estagiario` NUNCA adquirem Nível 2.**
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistência: gestor ausente do Nível 2 mas listado com permissão Protocolo/Finalizar na seção 9.

A linha 184 declara que o Nível 2 é adquirido por "Defensor/Coordenador/Admin", omitindo gestor. Porém, na matriz RBAC da seção 9 (linha 335), gestor aparece com Protocolo/Finalizar = ✅. Se gestor pode finalizar/protocolar, ele deveria também adquirir Nível 2 (defensor_id). Caso contrário, há uma contradição entre o modelo de locking e o de permissões. Recomendo alinhar para Defensor/Coordenador/Gestor/Admin ou esclarecer por que gestor realiza a finalização sem adquirir Nível 2.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@arquivos/Conhecimento/01-Referencia/ARCHITECTURE.md` at line 184, The text in
the Nível 2 description (the line that lists "Defensor/Coordenador/Admin" and
the `defensor_id` locking behavior) conflicts with the RBAC matrix in section 9
where `gestor` has Protocolo/Finalizar rights; update the Nível 2 line to either
include `Gestor` (i.e., "Defensor/Coordenador/Gestor/Admin") so `gestor`
acquires `defensor_id` and follows the same protocol/finalize locking, or add a
short clarifying sentence explaining why `gestor` can Protocolo/Finalizar
without acquiring Nível 2 and how locking/`defensor_id` is handled for that
role; reference the "Nível 2", `defensor_id`, and the RBAC "Protocolo/Finalizar"
entry for `gestor` when making the edit.

- **Isolamento de Unidade:** Middleware `requireSameUnit` bloqueia IDOR. **Admin e Gestor** possuem bypass global.
- **HTTP 423 (Locked):** Retorno padrão quando outro usuário detém o lock.
- **Unlock Privilegiado:** Administradores, Gestores e Coordenadores podem forçar destravamento via painel.
- **Auto-release:** Lock liberado após 30min de inatividade.
- **Manual Unlock:** Administradores podem forçar destravamento via painel.

---

Expand Down Expand Up @@ -329,15 +329,17 @@ sequenceDiagram

### Permissões por Cargo (RBAC)

| Cargo | Leitura | Escrita | Admin |
|:------|:--------|:--------|:------|
| `admin` | ✅ | ✅ | ✅ |
| `defensor` | ✅ | ✅ | ❌ |
| `estagiario` | ✅ | ✅ | ❌ |
| `recepcao` | ✅ | ✅ | ❌ |
| `visualizador` | ✅ | ❌ | ❌ |
| Cargo | Leitura | Escrita | Protocolo/Finalizar | Admin/Global | Unlock |
|:------|:--------|:--------|:--------------------|:-------------|:-------|
| `admin` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `gestor` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `coordenador` | ✅ | ✅ | ✅ | ❌ | ✅ |
| `defensor` | ✅ | ✅ | ✅ | ❌ | ❌ |
| `servidor` | ✅ | ✅ | ❌ | ❌ | ❌ |
| `estagiario` | ✅ | ✅ | ❌ | ❌ | ❌ |

> **Middleware:** `requireWriteAccess` bloqueia `visualizador` de operações POST/PATCH/DELETE com HTTP 403.
> **Middleware:** `requireWriteAccess` usa whitelist positiva: apenas `admin`, `gestor`, `coordenador`, `defensor`, `servidor`, `estagiario` passam.
> **Isolamento de Unidade:** Middleware `requireSameUnit` bloqueia IDOR. Admins e Gestores possuem bypass global.

---

Expand Down Expand Up @@ -489,6 +491,7 @@ QSTASH_NEXT_SIGNING_KEY=...
JWT_SECRET=64_chars_random_string
API_KEY_SERVIDORES=64_chars_random
SALARIO_MINIMO_ATUAL=1621.00
ALLOWED_ORIGINS=https://maesemacao.defsulbahia.com.br,https://maes-acao.vercel.app

# Frontend
VITE_API_URL=https://api.mutirao.dpe.ba.gov.br
Expand Down
Loading
Loading