# Etapa 4 — Banco de dados, modelo User e deploy via webhook

> Registro da sessão de 21/06/2026. Documenta o que foi feito, as dores que
> existiam antes, as dificuldades enfrentadas durante a execução e como cada uma
> foi resolvida.

## O que foi entregue nesta sessão

### 1. Etapa 4 do handoff — Banco de dados e modelo User
- **`app/database.py`** — `db = SQLAlchemy()` e `init_db(app)` que chama `db.init_app(app)`.
- **`app/models/user.py`** — modelo `User` com `id`, `email` (unique, not null),
  `name`, `password_hash` (nullable), `google_id` (nullable), `created_at`.
- **`app/app.py`** — chama `init_db(app)`, importa `User` dentro da factory e expõe
  o comando CLI `flask create-db` (roda `db.create_all()`).
- **`app/config.py`** — `DATABASE_URL` do `.env` mapeado para `SQLALCHEMY_DATABASE_URI`.
- **`.gitignore`** — banco local `app/instance/prumo.db` fora do git.
- Verificado: `flask create-db` cria o banco; `User.query.all()` retorna `[]`.

### 2. Novo mecanismo de deploy (não estava no handoff — surgiu de uma dor real)
- **`POST /health/deploy`** — endpoint no blueprint `health`, protegido por Bearer
  token, que roda `git pull origin main` + `pip install` + `touch tmp/restart.txt`
  direto no servidor.
- **`passenger_wsgi.py`** — passou a carregar o `.env` via `load_dotenv()`.
- **`deploy.py`** — reescrito: agora chama o endpoint HTTP em vez da API do cPanel.
- **`config.py`** / **`.env.example`** / **`deploy.ini`** — `DEPLOY_TOKEN` adicionado.

---

## A dor que existia antes desta etapa

**O deploy não era confiável.** O fluxo anterior do `deploy.py` chamava a API do
cPanel (`VersionControl/update` + `VersionControlDeployment/create`). O problema:
a chamada `VersionControl/update` **retornava `status: 1` (sucesso) sem de fato
executar o `git pull`**. Resultado: o script dizia "Deploy concluído" mas o código
novo nunca chegava ao servidor — era preciso entrar no cPanel e clicar manualmente
em "Update from Remote" toda vez.

O GitHub Actions (`deploy.yml`) resolveria isso via SSH, mas está **bloqueado por
pendência de billing**, então não era opção.

---

## Dificuldades enfrentadas e como foram resolvidas

### Dificuldade 1 — `flask create-db` criava o banco, mas sem a tabela `users`
**Sintoma:** `flask create-db` rodava sem erro, mas `User.query.all()` falhava com
`OperationalError: no such table: users`.
**Causa:** `db.create_all()` só cria as tabelas dos modelos **registrados no
metadata** do SQLAlchemy. Como `User` nunca era importado, o metadata estava vazio.
**Solução:** importar `from models.user import User` **dentro de `create_app()`**
(com `# noqa: F401`), garantindo o registro antes do `create_all()`. Deletei o
`prumo.db` vazio e recriei.

### Dificuldade 2 — onde o Flask cria o SQLite
**Sintoma:** `Test-Path app\prumo.db` retornava `False` mesmo após criar o banco.
**Causa:** o Flask grava o SQLite na pasta `instance/`, não na raiz de `app/`.
**Solução:** corrigi o `.gitignore` para `app/instance/prumo.db`.

### Dificuldade 3 — descobrir a porta SSH do cPanel (tentativa de fix permanente)
Para tornar o `deploy.py` confiável, a primeira ideia foi usar SSH direto (como o
GitHub Actions faz). Gerei um par de chaves ed25519, importei e autorizei a chave
pública no cPanel. Mas a conexão falhava em todas as portas testadas:
- Porta 22 → `Connection refused` (bloqueada por firewall da hospedagem).
- Porta 26 → respondia, mas era **SMTP (Exim)**, não SSH.
- Portas 2222, 65002, 22022, 2200, etc. → `timeout`.
- Um scan de portas só achou serviços web/FTP/DNS — nenhum SSH exposto.
- O terminal do cPanel é um ambiente **restrito (jail)**: sem `ss`, sem `netstat`
  útil, sem acesso a `/etc/ssh/sshd_config`.

**Conclusão:** SSH externo está bloqueado nesta hospedagem compartilhada. Abandonei
essa rota.

### Dificuldade 4 — alternativa ao SSH: endpoint de deploy no próprio app
Como o app já responde via HTTPS, criei um endpoint `POST /health/deploy` que faz o
trabalho do SSH (`git pull` + `pip install` + restart) de dentro do servidor,
protegido por um Bearer token secreto. O `deploy.py` virou um simples cliente HTTP.

### Dificuldade 5 — o endpoint subia, mas o token não era lido (`503 deploy not configured`)
**Sintoma:** o endpoint respondia, mas `current_app.config["DEPLOY_TOKEN"]` vinha
vazio.
**Causa:** em produção o Passenger usa `passenger_wsgi.py` diretamente — **não passa
por `flask run`**, que é quem normalmente carrega o `.env`. Logo o `.env` nunca era
lido.
**Solução:** adicionar `load_dotenv()` no topo do `passenger_wsgi.py`, apontando para
`app/.env`.

### Dificuldade 6 — Passenger não reiniciava sozinho após o "Update from Remote"
**Sintoma:** o código novo chegava ao servidor (confirmado via `tail` no arquivo),
mas o app continuava servindo a versão antiga; o `/health/deploy` dava 404.
**Causa:** "Update from Remote" do cPanel só faz o `git pull` — **não roda as tarefas
do `.cpanel.yml`** (que incluem o `touch tmp/restart.txt`).
**Solução:** durante o bootstrap, forçar o restart manualmente
(`touch tmp/restart.txt` no terminal). Depois que o endpoint `/health/deploy` passou
a existir e funcionar, ele mesmo cuida do restart — o problema desaparece.

---

## Estado final do fluxo de deploy

A partir de agora, o ciclo é:

```bash
git push origin main
python deploy.py        # POST /health/deploy → git pull + pip install + restart
```

O `deploy.py` agora mostra o diff real do `git pull` (ex.: `Fast-forward ...`),
confirmando que o código chegou — fim da incerteza do fluxo antigo.

### Observação sobre o bootstrap
As dificuldades 5 e 6 foram **dores de ovo-e-galinha**: o mecanismo que conserta o
deploy precisava, ele mesmo, ser deployado por um meio confiável. Por isso os
primeiros commits desta sessão ainda dependeram de `git pull` + `touch
tmp/restart.txt` manuais no terminal do cPanel. Do commit que corrigiu o
`load_dotenv` em diante, `python deploy.py` passou a funcionar sozinho.

---

## Commits da sessão
- `ff944b1` — feat: etapa 4 — SQLAlchemy + modelo User + comando create-db
- `9530bb0` — feat: endpoint POST /health/deploy para deploy via webhook
- `d67d73f` — fix: load_dotenv no passenger_wsgi para carregar .env em produção
- `e681b85` — docs: CLAUDE.md — deploy via webhook, etapa 4 concluída

---

## Comandos úteis (execução manual / troubleshooting)

Caminhos no servidor:
- App root: `/home/bianchin/public_html/app.prumoboard.com`
- venv pip: `/home/bianchin/virtualenv/public_html/app.prumoboard.com/3.13/bin/pip`
- `.env`: `/home/bianchin/public_html/app.prumoboard.com/app/.env`

### Deploy normal (o caminho feliz)
```bash
git push origin main
python deploy.py        # POST /health/deploy → git pull + pip install + restart
```

### Disparar o deploy direto via HTTP (sem o deploy.py)
PowerShell — token está em `deploy.ini [deploy] token`:
```powershell
$h = @{ Authorization = "Bearer <DEPLOY_TOKEN>" }
Invoke-WebRequest -Uri "https://app.prumoboard.com/health/deploy" -Method POST -Headers $h -TimeoutSec 60 | Select-Object StatusCode, Content
```
curl:
```bash
curl -i -X POST https://app.prumoboard.com/health/deploy -H "Authorization: Bearer <DEPLOY_TOKEN>"
```

### Deploy manual pelo terminal do cPanel (se o endpoint estiver fora do ar)
Fallback usado no bootstrap desta sessão — faz na mão o que o endpoint faz:
```bash
cd /home/bianchin/public_html/app.prumoboard.com
git pull origin main
source /home/bianchin/virtualenv/public_html/app.prumoboard.com/3.13/bin/activate
pip install --prefer-binary -r app/requirements.txt
touch tmp/restart.txt        # reinicia o Passenger
```

### Forçar só o restart do Passenger
```bash
touch /home/bianchin/public_html/app.prumoboard.com/tmp/restart.txt
```

### Confirmar que o app está no ar e em qual versão
```bash
curl https://app.prumoboard.com/health/hello      # {"message":"Hello Prumo"}
curl https://app.prumoboard.com/health/            # status + timestamp
```
Conferir, pelo terminal do cPanel, se o código novo realmente chegou:
```bash
tail -5 /home/bianchin/public_html/app.prumoboard.com/app/routes/health/health.py
git -C /home/bianchin/public_html/app.prumoboard.com log --oneline -3
```

### Diagnosticar "deploy not configured" (HTTP 503)
Significa que `DEPLOY_TOKEN` não foi lido. Verificar o `.env` no servidor:
```bash
cat /home/bianchin/public_html/app.prumoboard.com/app/.env
# se faltar a linha:
echo "DEPLOY_TOKEN=<TOKEN>" >> /home/bianchin/public_html/app.prumoboard.com/app/.env
touch /home/bianchin/public_html/app.prumoboard.com/tmp/restart.txt
```

### Banco de dados (local)
```bash
cd app
flask --app app create-db          # cria app/instance/prumo.db + tabela users
flask --app app shell
>>> from models.user import User
>>> User.query.all()
```

### Gerar um novo token de deploy
```bash
python -c "import secrets; print(secrets.token_hex(32))"
```
Depois atualizar nos dois lados: `deploy.ini [deploy] token` (local) e
`DEPLOY_TOKEN` no `.env` do servidor — e reiniciar o Passenger.

### Disparar as tarefas do .cpanel.yml via API (alternativa ao restart manual)
```powershell
$h = @{ Authorization = "cpanel bianchin:<CPANEL_API_TOKEN>" }
$root = [uri]::EscapeDataString("/home/bianchin/public_html/app.prumoboard.com")
Invoke-WebRequest -Uri "https://bianch.in:2083/execute/VersionControlDeployment/create?repository_root=$root" -Headers $h -UseBasicParsing
```
> Nota: `VersionControl/update` (o git pull via API) é o que se mostrou **não
> confiável** — retorna sucesso sem puxar o código. Prefira o `/health/deploy`.
