IDE visual em nuvem para computação quântica com Qiskit. O usuário arrasta blocos no canvas (Hadamard, CNOT, Measure, …), conecta, e clica em ▶ — o backend gera o script Python equivalente e executa em um sandbox isolado, retornando counts, stdout/stderr e histogramas em tempo quase real.
O alvo é tornar Qiskit acessível para quem está aprendendo computação quântica sem precisar escrever Python desde o primeiro dia. Cada nodo do canvas representa uma operação (gate, medição, simulação, plot) e edges definem a ordem de execução. Além do canvas, há também:
- Painel Details com Variables / Functions tipadas (Int/Float/Str/Bool/List/Dict)
- Aba Classes com decorators, herança, vars e métodos editáveis
- Terminal que é um REPL Python real rodando no sandbox, com as definições do arquivo já carregadas
- Console mostrando stdout + histogramas PNG inline depois de cada run
- Imports com catálogo de pacotes Python e nodos disponíveis
Domínio público alvo: node-forge.com.br (deploy ainda em planejamento).
- Funcionalidades
- Arquitetura
- Stack
- Pré-requisitos
- Rodando localmente
- Variáveis de ambiente
- Envio de email
- Primeiro Hello World
- Comandos úteis
- Testes
- CI/CD
- Estrutura do repositório
- Subindo pro GitHub
- Trabalhando em equipe
- Aviso sobre o executor
- Roadmap
- Licença
- Editor visual: drag-and-drop de gates quânticos no canvas (React Flow), edição de propriedades por nodo, undo/redo (
Ctrl+Z/Ctrl+Shift+Z) - Múltiplos arquivos por projeto: cada projeto pode ter vários circuitos (
Arquivo 1,Arquivo 2, …) - Codegen automático: grafo → script Python Qiskit válido
- Execução sandboxed: cada
▶spawn um container Docker efêmero (256 MB, 1 CPU, sem rede, timeout 30s, usernobody, capabilities removidas) - REPL Python integrado: aba Terminal roda Python real no sandbox; variáveis, funções e classes definidas no arquivo já vêm carregadas
- Multi-tenant auth: register/login com JWT em cookie httpOnly, mudar email/senha/username, recuperação de senha com email via Resend, rate limiting por IP
- Profile completo: avatar upload (PNG/JPEG/WebP/GIF, até 2 MB), Vault profile (nome, bio, site, redes sociais, país), edição inline
- Projects: grid de cards, duplicar projeto (clona arquivos), upload de thumbnail, rename, delete, context menu por click direito
- i18n: UI completamente bilíngue (PT-BR + EN) via next-intl, language switcher no header
- Async runs: polling do
GET /jobs/:idpara circuitos longos não travarem a conexão - Testes automatizados: suíte E2E com Playwright cobrindo register/login/Hello World ponta-a-ponta + pytest no backend cobrindo a superfície de auth (register, login, me, logout, rate limit)
- Headers de segurança: CSP, HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy em todas as respostas; CORSMiddleware no FastAPI com origens em allowlist explícita
┌────────────────────────┐
│ Browser do usuário │
│ (http://localhost:3000)│
└─────────────┬──────────┘
│ HTTPS only no futuro
▼
┌──────────────────────────────────────────────────────────────┐
│ frontend (Next.js 16, port 3000) │
│ - App Router + Server Components │
│ - BFF proxy em /api/* → backend │
│ - cookie httpOnly fica first-party (sem CORS) │
└─────────────┬────────────────────────────────────────────────┘
│ /api/* server-side ┐
▼ │
┌──────────────────────────────────────────┐ ┌──────────────▼──────────────┐
│ backend (FastAPI, port 8000) │ │ postgres (port 5432) │
│ - auth + projects + users + runner │◄──►│ - users, projects, │
│ - codegen (graph → Python) │ │ project_files, jobs, │
│ - rate limit (slowapi) │ │ password_reset_tokens │
│ - email (Resend backend) │ └─────────────────────────────┘
└─────────────┬────────────────────────────┘
│ POST /run (token-auth)
▼
┌──────────────────────────────────────────┐
│ executor (FastAPI + docker SDK, :8001) │
│ - exposto SÓ na executor-net interna │ ┌──────────────────────────┐
│ - spawna container efêmero por /run │────►│ runner ephemeral │
│ - mem 256m, 1 CPU, network_disabled, │ │ python -u script.py │
│ user=nobody, cap_drop=ALL, pids=100 │ │ (mesma imagem executor) │
│ - timeout 30s │ │ removido após exit │
└──────────────────────────────────────────┘ └──────────────────────────┘
Quatro serviços orquestrados via docker-compose.yml. O frontend é a única
origem que o browser acessa. O backend roda em rede interna e é alcançado via
o BFF do Next (que mantém o cookie httpOnly first-party). O executor spawna
containers efêmeros via docker.sock montado do host (root-equivalent — ver
aviso). O postgres persiste tudo num volume Docker.
| Camada | Tecnologia |
|---|---|
| Frontend | Next.js 16 (App Router) · React 19 · TypeScript · Tailwind v4 · shadcn/ui · @xyflow/react (React Flow) · next-intl · TanStack Query · Zustand · react-hook-form + zod |
| Backend | FastAPI · SQLAlchemy 2 async · asyncpg · Alembic · uv · pyjwt · bcrypt · slowapi · httpx |
| Executor | FastAPI · Docker SDK · Qiskit · qiskit-aer · matplotlib · numpy |
| DB | Postgres 16 |
| Infra dev | Docker Compose |
| Resend (transacional) |
A única coisa obrigatória é Docker. Node e Python só são necessários se
você quiser desenvolver fora do container (rodar testes, gerar uv.lock, etc.).
- Docker Desktop for Windows com backend WSL 2 (instalador ativa por padrão)
- WSL 2 (
wsl --installno PowerShell) - Git for Windows
Recomendado: clonar o projeto dentro do WSL (ex: ~/projects/node-forge)
em vez de em C:\…. A performance do bind mount entre Windows e WSL é bem
melhor lá dentro.
- Docker Desktop for Mac (Intel ou Apple Silicon — ambos suportados)
- Git já vem instalado; senão
xcode-select --install
- Docker Engine + plugin
docker compose(não a versão velha em Python) - Adiciona seu usuário ao grupo
dockerpra não precisar desudo:sudo usermod -aG docker $USER newgrp docker - Git via package manager
- Node 20+ e pnpm 10+ — só se quiser rodar
pnpm install/pnpm lintno host - Python 3.12+ e uv — só se quiser regenerar
uv.lockou rodaralembicno host
git clone https://github.com/<seu-usuario>/node-forge.git
cd node-forgecp .env.example .envAbre .env e ajusta os campos sensíveis — veja a tabela de variáveis.
Pelo menos altere JWT_SECRET e INTERNAL_API_TOKEN com valores únicos.
Para gerar uma string aleatória:
# Linux / macOS / WSL
openssl rand -hex 32
# Windows PowerShell
[System.Convert]::ToBase64String((1..32 | %{Get-Random -Maximum 256}))docker compose up -d --buildO primeiro build leva ~3-5 minutos (qiskit + matplotlib pesam). Vai puxar Postgres 16, build do backend (FastAPI), frontend (Next.js + node_modules), e executor (qiskit + qiskit-aer + matplotlib).
Quando o postgres ficar healthy:
docker compose exec backend uv run alembic upgrade head| Serviço | URL | Observação |
|---|---|---|
| frontend | http://localhost:3000 | Única origem que o browser deve usar |
| backend | http://localhost:8000 | Acesso direto só pra debug com curl; a UI usa o proxy do Next |
| executor | :8001 (interno) | Não exposto ao host por design |
| postgres | localhost:5432 | Credenciais no .env |
Crie uma conta em /register e parta pro primeiro Hello World.
Tudo vem do arquivo .env na raiz do repositório, carregado automaticamente
pelo Docker Compose via env_file: .env.
⚠️ .envestá em.gitignoree nunca deve ser commitado. Use o.env.example(que está versionado) como template. Cada dev mantém o seu próprio.envlocal; segredos compartilhados (chaves de API de produção, tokens reais) ficam num gerenciador de segredos do time (1Password, Vault, AWS Secrets Manager, …), não num arquivo de texto.
| Variável | Para que serve | Default | Você precisa mudar? |
|---|---|---|---|
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB |
Credenciais e nome do banco do Postgres local | postgres / postgres / nodeforge |
Não em dev. Em produção, sim |
DATABASE_URL |
URL de conexão do backend ao Postgres | postgresql+asyncpg://postgres:postgres@postgres:5432/nodeforge |
Não em dev |
JWT_SECRET |
Chave HMAC pra assinar tokens JWT do cookie de auth | placeholder | Sim, sempre. Gere uma única por ambiente |
JWT_ALGORITHM |
Algoritmo de assinatura | HS256 |
Não |
JWT_EXPIRE_DAYS |
Duração do cookie de sessão | 7 |
A gosto |
EXECUTOR_URL |
URL interna do serviço executor | http://executor:8001 |
Não |
EXECUTOR_TIMEOUT_SECONDS |
Limite máx de execução de um run | 30 |
A gosto |
CORS_ORIGINS |
Origens permitidas (hoje só localhost; o proxy BFF substitui em prod) | http://localhost:3000 |
Sim em prod |
COOKIE_SECURE |
Flag Secure do cookie httpOnly |
false (dev) |
true em prod |
INTERNAL_API_TOKEN |
Segredo compartilhado entre Next BFF e FastAPI (defense-in-depth) | placeholder | Sim, sempre. Único por ambiente |
EMAIL_BACKEND |
Backend de envio de email | stdout (printa link no log) |
Troca pra resend em prod |
EMAIL_FROM |
Remetente dos emails | Node-Forge <onboarding@resend.dev> |
Troca pelo seu domínio verificado |
RESEND_API_KEY |
API key da Resend | vazio | Sim, se EMAIL_BACKEND=resend |
APP_BASE_URL |
URL pública usada para links em emails | http://localhost:3000 |
Sim em prod |
BACKEND_INTERNAL_URL |
URL que o Next usa server-side pro backend | http://backend:8000 |
Não |
NEXT_PUBLIC_API_URL |
URL placeholder pra curl no host | http://localhost:8000 |
Não |
NEXT_TELEMETRY_DISABLED |
Desliga telemetria anônima do Next | 1 |
Não |
Pra rodar em dev local, depois de cp .env.example .env o mínimo que você
precisa editar é:
JWT_SECRET=<gere-uma-string-aleatoria>
INTERNAL_API_TOKEN=<gere-outra-string-aleatoria>Pra enviar emails reais (recuperação de senha funcionar), adicione também:
EMAIL_BACKEND=resend
RESEND_API_KEY=re_xxxxxxxxxxxxx
EMAIL_FROM="Node-Forge <no-reply@seudominio.com.br>"Pra produção mude tudo o que diz "Sim" na coluna da tabela.
💡 Truque útil: depois de editar
.env, sempre usedocker compose up -d backend(recria o container) — nãodocker compose restart, que reusa o container existente com oenv_filevelho. Esse é um gotcha clássico do compose.
Por padrão o backend roda com EMAIL_BACKEND=stdout — o link de recuperação
aparece em docker compose logs backend em vez de ir pra um email real. Útil
em dev e testes locais, não precisa nem criar conta na Resend.
Para ativar envio real via Resend (free tier: 100/dia, 3000/mês):
- Crie conta em resend.com
- Em API Keys → gere uma key (formato
re_xxxxxxxxxx) - (Opcional, mas necessário pra mandar pra qualquer email) Em Domains → adicione
seudominio.com.br, configure os registros DNS (SPF, DKIM) que eles vão exibir, e clica em "Verify" - No
.env:EMAIL_BACKEND=resend RESEND_API_KEY=re_xxxxxxxxxxxx EMAIL_FROM="Node-Forge <no-reply@seudominio.com.br>" docker compose up -d backend(recria — não userestart)
Gotcha do free tier: se você usar
EMAIL_FROM="…<onboarding@resend.dev>"(o sender de teste deles), só consegue enviar pro email da própria conta Resend. Pra mandar pra qualquer destinatário, precisa do domínio verificado.
Depois de subir a stack, crie uma conta e siga estes passos pra rodar o Bell state Φ⁺ — o Hello World canônico da computação quântica:
- Em
/projects, clique no tile laranja+e crie um projeto chamadoHello World - No editor, vai pro painel
Inspecte configuraQubits = 2,Bits clássicos = 2,Shots padrão = 1000 - Da paleta
Detailsarrasta na ordem:- Hadamard (H) —
qubit_index = 0 - CNOT —
control_qubit = 0,target_qubit = 1 - Measure —
qubit_index = 0,classical_index = 0 - Measure —
qubit_index = 1,classical_index = 1 - Run (Aer Simulator) —
shots = 1000 - Plot Histogram
- Hadamard (H) —
- Conecte na ordem: H → CNOT → Measure → Measure → Run → Plot
- Click no ▶ verde no canto superior direito do canvas
Resultado esperado em ~3 segundos no Console: Counts: {'00': ~500, '11': ~500}
e um histograma com duas barras. Nunca vai aparecer 01 ou 10 — isso é
emaranhamento quântico operando.
# Logs em tempo real
docker compose logs -f backend
docker compose logs -f executor
docker compose logs -f frontend
# Status dos serviços
docker compose ps
# Parar tudo (mantém dados do postgres no volume)
docker compose down
# Parar e apagar volumes (zera o banco)
docker compose down -v
# Rebuild (após mudança em Dockerfile ou pyproject.toml)
docker compose up -d --build
# Rebuild de um serviço só
docker compose up -d --build backend
# Shell dentro do backend
docker compose exec backend bash
# Rodar uma migration nova
docker compose exec backend uv run alembic upgrade head
# Criar uma nova migration
docker compose exec backend uv run alembic revision -m "descricao"
# Type-check frontend
docker compose exec frontend pnpm exec tsc --noEmit
# Lint frontend
docker compose exec frontend pnpm lint
# Lint backend
uv run --project backend ruff check backend/src/
# E2E (Playwright, roda do host contra a stack já no ar)
cd frontend && pnpm test:e2e
# Pytest (backend, exige nodeforge_test DB no postgres local)
cd backend && DATABASE_URL_TEST="postgresql+asyncpg://postgres:postgres@localhost:5432/nodeforge_test" uv run pytestDuas camadas, cada uma cobrindo um risco diferente:
3 specs em frontend/tests/e2e/ que rodam contra a stack inteira via Docker
Compose. Cobre os caminhos críticos que quebrariam o produto se regredissem:
auth.spec.ts— proxy redirect quando deslogado (/projects→/login?next=), loop completo de register → login → projects → logouthello-world.spec.ts— registra um usuário, semeia um circuito Bell state via BFF, executa no sandbox e valida que o Aer simulator retornou só contagens|00>e|11>(nunca|01>ou|10>— semântica quântica do emaranhamento)
Primeiro uso instala o Chromium:
cd frontend
pnpm install
pnpm test:e2e:install # baixa Chromium + libs do sistema
pnpm test:e2e # roda os 3 specs (~15s)O browser fica fixado em pt-BR (locale + Accept-Language) pra os
seletores casarem com os textos do produto. Para abrir o painel de
debug interativo do Playwright: pnpm test:e2e:ui.
12 testes em backend/tests/test_auth.py cobrindo register/login/me/logout
- as branches de erro (duplicado, senha curta, email inválido, senha errada, email desconhecido). Um teste dedicado liga o rate limiter de volta e prova que o teto de 5/minute realmente devolve 429 na 6ª tentativa.
# Cria o DB de teste uma vez:
docker compose exec postgres psql -U postgres -c "CREATE DATABASE nodeforge_test;"
# Roda:
cd backend
DATABASE_URL_TEST="postgresql+asyncpg://postgres:postgres@localhost:5432/nodeforge_test" \
uv run pytestA conftest desliga o rate limiter (RATE_LIMIT_ENABLED=false) pro grosso
da suíte poder empilhar requests sem dormir entre cada um. A produção
nunca seta essa env var.
GitHub Actions roda em todo push pra main e em todo PR — .github/workflows/ci.yml.
Quatro jobs em paralelo:
| Job | O que faz |
|---|---|
| frontend | pnpm install + lint + typecheck + build |
| backend | uv sync + ruff check + ruff format --check + pytest (com postgres:16-alpine como service container) |
| executor | uvx ruff check + uvx ruff format --check |
| e2e | Sobe a stack inteira via Docker Compose, instala Chromium, roda a suíte Playwright, publica o HTML report como artifact em todo run |
Concurrency é agrupada por ref — push novo cancela run em andamento da mesma branch.
- Dependabot (
.github/dependabot.yml) escaneia toda semana (segunda 06:00 America/Sao_Paulo) cada superfície de dependência: GitHub Actions, npm emfrontend/, pip embackend/eexecutor/, e bases de imagem Docker. Minor/patch são agrupados; major vem como PR individual. - CodeQL (
.github/workflows/codeql.yml) analisajavascript-typescriptepythonem todo push/PR e re-roda no cron de sábado 09:00 UTC. Queriessecurity-extended+security-and-quality.
node-forge/
├── .github/
│ ├── workflows/ # ci.yml (4-job pipeline) + codeql.yml
│ └── dependabot.yml # Weekly Monday scans
│
├── frontend/ # Next.js 16 App Router
│ ├── tests/e2e/ # Playwright specs + helpers
│ ├── playwright.config.ts
│ ├── src/
│ │ ├── app/
│ │ │ ├── [locale]/ # Rotas localizadas (pt-BR | en)
│ │ │ │ ├── (auth)/ # login, register, forgot-password, reset-password
│ │ │ │ └── (app)/ # projects, profile, projects/[id]
│ │ │ └── api/[...path]/ # BFF proxy → backend
│ │ ├── components/
│ │ │ ├── brand/ # Logo, Wordmark
│ │ │ ├── editor/ # Canvas, palette, panels, terminal, etc.
│ │ │ ├── profile/ # Profile + dialogs
│ │ │ └── ui/ # shadcn primitives
│ │ ├── i18n/ # next-intl wiring (routing, navigation, request)
│ │ ├── lib/ # server-api, graph-types, imports-catalog
│ │ ├── proxy.ts # Auth gate + locale routing
│ │ └── stores/editor.ts # Zustand store (com undo/redo)
│ ├── messages/ # pt-BR.json + en.json
│ ├── next.config.ts
│ └── Dockerfile
│
├── backend/ # FastAPI + SQLAlchemy
│ ├── tests/ # pytest suite (auth coverage)
│ ├── src/
│ │ ├── auth/ # login, register, change me, forgot/reset, rate limit
│ │ ├── projects/ # CRUD + files + thumbnail + duplicate
│ │ ├── users/ # profile + avatar
│ │ ├── runner/ # codegen (graph → python) + /run + /terminal + /jobs
│ │ ├── email/ # Resend sender + templates
│ │ ├── db/ # SQLAlchemy models + session
│ │ └── config.py # Settings (pydantic-settings)
│ ├── alembic/
│ │ └── versions/ # 0001..0007 (users, projects, jobs, files, profile, thumbnail, password reset)
│ ├── pyproject.toml
│ ├── uv.lock
│ └── Dockerfile
│
├── executor/ # FastAPI sandboxed runner
│ ├── src/main.py # POST /run — spawn ephemeral container
│ ├── pyproject.toml
│ └── Dockerfile
│
├── infra/ # Terraform stubs (TODO)
├── docker-compose.yml
├── .env.example # Template versionado
├── .env # SEU env local (gitignored)
├── .gitignore
└── README.md
Se você ainda não tem o repo remoto criado, faça pela UI do GitHub
(https://github.com/new) com nome node-forge, deixe vazio (sem README,
sem .gitignore, sem licença) — esse repo já tem tudo isso.
Depois, no terminal na raiz do projeto:
# Confirma que .env não está sendo trackeado (precisa estar VAZIO o output)
git status | grep -F ".env"
# Adiciona o remote (use SSH se você tem chave configurada, ou HTTPS)
git remote add origin git@github.com:<seu-usuario>/node-forge.git
# ou
git remote add origin https://github.com/<seu-usuario>/node-forge.git
# Push da branch main
git push -u origin mainA partir daí, fluxo padrão de git (git pull, git push, git checkout -b feature/...).
Antes do primeiro push, dá uma olhada no histórico: se em algum commit passado um
.env(ou outro segredo) foi commitado por engano, rotacione as chaves afetadas — o histórico do Git é público depois que você sobe o repo, mesmo se o arquivo for deletado depois.
Quando outro dev do time clona o repo, ele não recebe seu .env (está
gitignored, por design — ele contém JWT_SECRET, RESEND_API_KEY e outros
segredos). O setup do colega é:
git clone https://github.com/<owner>/node-forge.git
cd node-forge
cp .env.example .env
# editar .env com valores próprios (geração de JWT_SECRET com openssl etc.)
docker compose up -d --build
docker compose exec backend uv run alembic upgrade headPra compartilhar segredos entre time (ex: chave Resend de staging, JWT secret de produção), use:
- Curto prazo / time pequeno: 1Password, Bitwarden Family, ou outro password manager com vault compartilhado
- Médio prazo: Doppler ou Infisical — UI bonita, integra com Docker Compose, injeta env em CI/CD
- Em produção: AWS Secrets Manager, GCP Secret Manager, Vault da HashiCorp, ou GitHub Actions Secrets pro pipeline de deploy
Nunca mande segredos por Slack, email ou Notion — mesmo "só por um minuto". Histórico de mensagem fica retido.
- Cria branch local:
git checkout -b feat/coisa-nova - Trabalha, commita (Conventional Commits:
feat:,fix:,chore:, etc.) git push -u origin feat/coisa-nova- Abre PR no GitHub apontando pra
main - Outro dev revisa, dá aprovação, merge (squash recomendado pra manter histórico limpo)
O serviço executor monta /var/run/docker.sock pra subir containers efêmeros
com Qiskit. Isso dá acesso root-equivalente à máquina host. Em desenvolvimento
na sua máquina é aceitável (foi o trade-off pra ter sandbox real sem precisar
de tooling avançada). Mas este design não é seguro pra produção sem
substituição por sandbox de verdade — gVisor, Firecracker microVMs, rootless
Docker, ou uma VM descartável por execução.
Não suba este docker-compose.yml como está em servidor compartilhado nem
em produção sem ler Aviso de produção e tomar uma das ações de
hardening listadas.
- ✅ Editor visual de circuitos (multi-arquivo por projeto)
- ✅ Codegen + execução sandboxed via Aer Simulator
- ✅ REPL Python no Terminal com preamble do arquivo
- ✅ Variables, Functions, Classes editáveis com codegen
- ✅ Undo/redo (Ctrl+Z / Ctrl+Y)
- ✅ Auth completa (register, login, change me, forgot/reset com email)
- ✅ Rate limiting nas rotas de auth
- ✅ Profile com avatar upload (PNG/JPEG/WebP/GIF)
- ✅ Projects com duplicar, rename, thumbnail upload, context menu
- ✅ UI bilíngue PT-BR + EN
- ✅ Suíte E2E (Playwright) + pytest backend cobrindo auth
- ✅ CI/CD GitHub Actions (lint + typecheck + build + tests + e2e em todo PR)
- ✅ Dependabot semanal + CodeQL (security-extended)
- ✅ Security headers no Next (CSP, HSTS, Frame-Options, Permissions-Policy) + CORS no FastAPI
- Pause / Stop reais no botão de run (hoje são stubs visuais)
- Stream de stdout em tempo real (WebSocket) — hoje vem em batch no final
- Mais nodos no catálogo (Toffoli, SWAP, gates parametrizados RX/RY/RZ, classical control)
- Welcome email no register
- Variables/Functions integrando como nodos no canvas (drag-to-call)
- Substituir
docker.sockpor sandbox seguro (gVisor / Firecracker / rootless docker) — maior risco aberto JWT_SECRET/INTERNAL_API_TOKENvia secret manager (Doppler, AWS Secrets Manager, Vault), não.env- Postgres gerenciado (RDS, Cloud SQL, Supabase)
- TLS no domínio com Let's Encrypt + reverse proxy (Caddy ou Traefik)
- Pipeline de deploy disparado pelo CI (test → build → deploy)
- Infraestrutura como código em
infra/(Terraform para Oracle Cloud ou AWS) - Observabilidade: logs estruturados, métricas, tracing (OpenTelemetry)
A definir. Sugestão: MIT pro código + CC-BY-SA pros assets de design (logo, copy).
Adicione um arquivo LICENSE na raiz antes de tornar o repo público se quiser
deixar explícito.