pg-api 0.3.6

A high-performance PostgreSQL REST API driver with rate limiting, connection pooling, and observability
# PG-API

[![Rust](https://img.shields.io/badge/rust-2024-orange.svg)](https://www.rust-lang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
[![Version](https://img.shields.io/badge/version-0.3.0-green.svg)](CHANGELOG.md)

> Um driver PostgreSQL de alta performance via REST API, construído em Rust.

PG-API permite executar queries SQL, transações e gerenciar bancos de dados PostgreSQL através de uma API REST simples e segura, com autenticação via API keys, rate limiting e connection pooling.

---

## ✨ Funcionalidades

- **🔌 Query Execution** - Execute queries SQL via HTTP POST
- **📦 Batch Operations** - Múltiplas queries em uma única requisição
- **🔒 Transações** - Suporte a transações ACID
- **🔑 Autenticação** - API keys com controle de acesso granular
- **⏱️ Rate Limiting** - Limitação de requisições por conta
- **🏊 Connection Pooling** - Pool de conexões PostgreSQL eficiente
- **📊 Observabilidade** - Métricas opcionais para OpenSearch
- **📖 OpenAPI** - Documentação automática em `/docs`

---

## 🔒 Segurança (v0.3.0)

- **Fail-closed**: sem `config/accounts.json` válido o servidor não inicia (sem credenciais padrão).
- **API keys com hash SHA-256** em repouso; chaves legadas em texto puro continuam funcionando com aviso de rotação.
- **Gate SQL real**: 1 statement por request, `WITH` com escrita exige permissão de escrita, `COPY`/controle transacional/comandos de sessão e funções perigosas (`pg_read_file`, `dblink`) negados.
- **`options` aplicadas**: `statement_timeout` (default 60s, teto 300s) e `read_only`.
- **Transação real** em `/v1/transaction`; batch/transação limitados a 50 itens.
- **CORS e limites** (`tamanho do body`, timeout) lidos do `server.json`; rode atrás de TLS (ex.: nginx).

⬆️ **Migrando de 0.2.x**: garanta que `accounts.json` existe, rode com a mesma config, troque `PG_API_KEY` quando puder e trate os novos códigos `MULTI_STATEMENT_NOT_ALLOWED`, `STATEMENT_NOT_ALLOWED` e `STATEMENT_TIMEOUT`. Detalhes no [CHANGELOG](CHANGELOG.md).

---

## 🚀 Quick Start

### 1. Instalação

```bash
# Clone o repositório
git clone https://gitlab.com/aerunti/pg-api.git
cd pg-api

# Build do projeto
cargo build --release
```

### 2. Configuração

```bash
# Crie a configuração de contas
mkdir -p config
cat > config/accounts.json << 'EOF'
{
  "accounts": [
    {
      "id": "acc_001",
      "name": "Default Account",
      "api_keys": ["sk_test_123456789"],
      "role": "owner",
      "rate_limit": 1000,
      "max_connections": 50,
      "databases": [
        {
          "database": "postgres",
          "username": "postgres",
          "password": "postgres",
          "permissions": ["SELECT", "INSERT", "UPDATE", "DELETE"]
        }
      ]
    }
  ]
}
EOF

# Configuração do servidor
cat > config/server.json << 'EOF'
{
  "host": "127.0.0.1",
  "port": 8580,
  "log_level": "info"
}
EOF
```

### 3. Execute

```bash
# Modo desenvolvimento
cargo run

# Modo produção
./target/release/pg-api
```

O servidor estará disponível em `http://localhost:8580`

---

## 📖 Documentação

- **[Quick Start Guide]docs/QUICKSTART.md** - Tutorial passo a passo
- **[API Reference]docs/API.md** - Documentação completa dos endpoints
- **[Observability Guide]docs/OBSERVABILITY.md** - Configuração de métricas
- **[Changelog]CHANGELOG.md** - Histórico de versões

---

## 🛠️ Exemplo de Uso

### cURL

```bash
# Health check
curl http://localhost:8580/health

# Executar uma query
curl -X POST http://localhost:8580/v1/query \
  -H "X-API-Key: sk_test_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "database": "postgres",
    "query": "SELECT version()"
  }'
```

### Python

```python
import requests

client = requests.Session()
client.headers.update({"X-API-Key": "sk_test_123456789"})

# Executar query
response = client.post(
    "http://localhost:8580/v1/query",
    json={
        "database": "postgres",
        "query": "SELECT * FROM users WHERE id = $1",
        "params": [1]
    }
)
print(response.json())
```

### Rust

```rust
use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client
        .post("http://localhost:8580/v1/query")
        .header("X-API-Key", "sk_test_123456789")
        .json(&json!({
            "database": "postgres",
            "query": "SELECT version()"
        }))
        .send()
        .await?;
    
    println!("{}", response.text().await?);
    Ok(())
}
```

Veja mais exemplos em [`examples/`](examples/).

---

## 📋 Endpoints Principais

| Método | Endpoint | Descrição |
|--------|----------|-----------|
| GET | `/health` | Health check |
| POST | `/v1/query` | Executar query SQL |
| POST | `/v1/batch` | Executar batch de queries |
| POST | `/v1/transaction` | Executar transação |
| GET | `/v1/databases` | Listar databases |
| GET | `/v1/account` | Informações da conta |
| GET | `/docs` | Documentação Swagger UI |

---

## 🏗️ Arquitetura

```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Cliente   │────▶│   Axum/HTTP │────▶│  Middleware │
│  (cURL/JS)  │     │    Server   │     │(Auth/Rate)  │
└─────────────┘     └─────────────┘     └──────┬──────┘
                                               │
                         ┌─────────────────────┘
                         ▼
                ┌─────────────────┐
                │  Query Handler  │
                └────────┬────────┘
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
      ┌─────────┐  ┌─────────┐  ┌─────────┐
      │  Pool   │  │  Batch  │  │   Tx    │
      │ Manager │  │ Handler │  │ Handler │
      └────┬────┘  └─────────┘  └─────────┘
           │
           ▼
      ┌─────────┐
      │PostgreSQL│
      └─────────┘
```

---

## ⚙️ Configuração

### Configuração efetiva

```bash
# Servidor
APP__ADDR=127.0.0.1:8580
APP__LOG_LEVEL=info

# O servidor e o pool são configurados em arquivos JSON.
# CONFIG_DIR aponta para o diretório que contém:
#   server.json  (host/port)
#   pool.json    (max_connections, min_idle, worker_threads)
#   accounts.json (database, username, password, permissions)
CONFIG_DIR=/etc/pg-api

# Observabilidade (opcional)
OPENSEARCH_ENABLED=false
OPENSEARCH_API_URL=https://opensearch.example.com
OPENSEARCH_API_TOKEN=sk_live_xxxxx
```

O `accounts.json` é obrigatório. Cada database deve apontar para o PostgreSQL
real que o processo do PG-API alcança pela rede; criar um banco em outro host
não o torna automaticamente disponível. O `pool.json` limita conexões por
instância do PG-API e não aumenta `max_connections` do PostgreSQL. Configure
`pool.max_connections` abaixo do limite do PostgreSQL, preservando conexões
para outros serviços.

Para claims concorrentes, use `/v1/transaction` com uma transação real e
`SELECT ... FOR UPDATE SKIP LOCKED`; não use `/v1/batch`, pois batch não é
atômico. O corpo de `/v1/transaction` é um array de `QueryRequest`, cada item
contendo `database`, `query` e opcionalmente `params`.

---

## 🧪 Testes

```bash
# Rodar todos os testes
cargo test

# Rodar com output detalhado
cargo test -- --nocapture

# Benchmarks
cargo bench
```

---

## 🤝 Contribuindo

1. Fork o projeto
2. Crie sua branch (`git checkout -b feature/nova-feature`)
3. Commit suas mudanças (`git commit -am 'feat: nova feature'`)
4. Push para a branch (`git push origin feature/nova-feature`)
5. Abra um Pull Request

---

## 📄 Licença

Este projeto está licenciado sob a licença MIT - veja [LICENSE.md](LICENSE.md) para detalhes.

---

## 🔗 Links

- **Repositório**: https://gitlab.com/aerunti/pg-api
- **Documentação API**: http://localhost:8580/docs (quando rodando)
- **OpenAPI Spec**: http://localhost:8580/openapi.json

---

<p align="center">
  Desenvolvido com ❤️ por <strong>Aerun Serviços de Tecnologia</strong>
</p>