239 lines
9.8 KiB
Markdown
239 lines
9.8 KiB
Markdown
# Save in Med BFF
|
|
|
|
BFF para a plataforma Save in Med, integrando serviços do Appwrite (Databases) e expondo rotas de categorias, subcategorias, usuários, empresas, endereços, produtos de catálogo, carrinhos, pedidos e pagamentos.
|
|
|
|
## Pré-requisitos
|
|
- Python 3.12+
|
|
- Docker (opcional)
|
|
|
|
## Configuração & Segurança
|
|
A aplicação utiliza variáveis de ambiente carregadas via arquivo `.env`. Utilize o arquivo `.env.example` como base.
|
|
|
|
Variáveis obrigatórias:
|
|
- `APPWRITE_ENDPOINT`: URL do serviço Appwrite.
|
|
- `APPWRITE_PROJECT_ID`: identificador do projeto Appwrite.
|
|
- `APPWRITE_API_KEY`: chave da API Appwrite com permissões para Databases.
|
|
- `APPWRITE_DATABASE_ID`: identificador do banco de dados utilizado.
|
|
- `APPWRITE_COLLECTION_PRODUTOS_CATALOGO_ID`: identificador da coleção de produtos de catálogo.
|
|
- `APPWRITE_COLLECTION_LABORATORIOS_ID`: identificador da coleção de laboratórios.
|
|
- `APPWRITE_COLLECTION_CATEGORIAS_ID`: identificador da coleção de categorias.
|
|
- `APPWRITE_COLLECTION_SUBCATEGORIAS_ID`: identificador da coleção de subcategorias de produtos.
|
|
- `APPWRITE_COLLECTION_CARRINHOS_ID`: identificador da coleção de carrinhos.
|
|
- `APPWRITE_COLLECTION_PEDIDOS_ID`: identificador da coleção de pedidos.
|
|
- `APPWRITE_COLLECTION_USUARIOS_ID`: identificador da coleção de usuários.
|
|
- `APPWRITE_COLLECTION_EMPRESAS_ID`: identificador da coleção de empresas.
|
|
- `APPWRITE_COLLECTION_EMPRESAS_DADOS_ID`: identificador da coleção com os dados enriquecidos de empresas (ReceitaWS).
|
|
- `APPWRITE_COLLECTION_ENDERECOS_ID`: identificador da coleção de endereços.
|
|
- `APPWRITE_COLLECTION_ENTREGAS_ID`: identificador da coleção de entregas.
|
|
- `MAPBOX_ACCESS_TOKEN`: token de acesso para geocodificação automática dos endereços das empresas.
|
|
|
|
Variáveis opcionais:
|
|
- `SECURITY_API_KEY`: chave para proteger as rotas com o header `x-api-key`.
|
|
- `CORS_ALLOW_ORIGINS`: lista separada por vírgula de origens autorizadas no CORS. Se vazia, `*` é utilizado.
|
|
|
|
### Campos adicionais na coleção `empresas-dados` (Appwrite)
|
|
Crie os atributos abaixo na coleção `empresas-dados` para suportar frete e geocodificação automática:
|
|
|
|
- `tipo-frete` (string, ex.: `gratis`, `fixo`, `por_km`)
|
|
- `taxa-entrega` (double/decimal)
|
|
- `valor-frete-km` (double/decimal)
|
|
- `raio-entrega-km` (double/decimal)
|
|
- `latitude` (double/decimal)
|
|
- `longitude` (double/decimal)
|
|
|
|
Os novos campos serão preenchidos automaticamente a partir do endereço relacionado (`enderecos`).
|
|
|
|
Variáveis de autenticação e sessão:
|
|
- `JWT_SECRET`: segredo para assinar/verificar JWT.
|
|
- `JWT_ALGORITHM`: algoritmo (ex.: `HS256`).
|
|
- `JWT_ACCESS_TTL_SECONDS`: validade do token (segundos).
|
|
- `SESSION_COOKIE_NAME`: nome do cookie httpOnly.
|
|
- `COOKIE_SECURE`: `true` para setar cookie com `Secure` (produção HTTPS).
|
|
- `COOKIE_DOMAIN`: domínio do cookie (opcional).
|
|
- `SUPERADMIN_IDENTIFICADOR` e `SUPERADMIN_SENHA`: credenciais para criar usuário superadmin no startup. A senha é armazenada com `bcrypt_sha256`, permitindo valores longos (recomendado usar frases secretas robustas). Não utilize chaves de API como senha.
|
|
|
|
Quando `SECURITY_API_KEY` está definido com um valor não vazio, todas as rotas passam a exigir o header `x-api-key` correspondente. Essa validação é feita via middleware, portanto o header **não** aparece na documentação do Swagger. Caso a variável esteja vazia ou ausente, nenhuma checagem é feita — ideal para ambientes de desenvolvimento.
|
|
|
|
## Endpoints
|
|
- `GET /` → `{ "message": "Hello, World!" }`
|
|
- `GET /healthz` → `{ "status": "ok" }`
|
|
- `GET /api/v1/ping` → `{ "pong": true }`
|
|
- `POST /api/v1/auth/register`
|
|
- `POST /api/v1/auth/login`
|
|
- `POST /api/v1/auth/logout`
|
|
- `GET /api/v1/auth/me`
|
|
- `GET /api/v1/categorias`
|
|
- `GET /api/v1/subcategorias`
|
|
- `GET /api/v1/usuarios`
|
|
- `GET /api/v1/empresas`
|
|
- `GET /api/v1/enderecos`
|
|
- `GET /api/v1/produtos-catalogo`
|
|
- `GET /api/v1/carrinhos`
|
|
- `GET /api/v1/pedidos`
|
|
|
|
## Como rodar em dev
|
|
```bash
|
|
pip install -r requirements.txt
|
|
uvicorn src.main:app --reload
|
|
|
|
# Docker
|
|
docker build -t saveinmed-bff .
|
|
docker run -p 8000:8000 --env-file .env saveinmed-bff
|
|
```
|
|
|
|
## Testes rápidos com curl
|
|
```bash
|
|
curl http://localhost:8000/
|
|
curl http://localhost:8000/healthz
|
|
curl http://localhost:8000/api/v1/ping
|
|
curl http://localhost:8000/api/v1/carrinhos
|
|
curl http://localhost:8000/api/v1/pedidos
|
|
```
|
|
|
|
> Adicione o header `-H "x-api-key: <token>"` nos exemplos abaixo quando a variável `SECURITY_API_KEY` estiver configurada.
|
|
|
|
### Auth
|
|
```bash
|
|
# Registro de usuário (senha é armazenada com hash no Appwrite)
|
|
curl -X POST "http://localhost:8000/api/v1/auth/register" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"identificador": "user@example.com", "nome": "Nome do Usuário", "senha": "senha123"}'
|
|
|
|
# Login retorna o JWT e seta o cookie httpOnly
|
|
curl -i -X POST "http://localhost:8000/api/v1/auth/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"identificador": "user@example.com", "senha": "senha123"}'
|
|
|
|
# Após o login, o cookie é enviado automaticamente pelo navegador/Swagger
|
|
curl -b cookiejar.txt -c cookiejar.txt http://localhost:8000/api/v1/auth/me
|
|
|
|
# Logout remove o cookie httpOnly
|
|
curl -X POST http://localhost:8000/api/v1/auth/logout
|
|
```
|
|
|
|
> Rotas públicas: `/`, `/healthz`, `/api/v1/ping` e todas as rotas sob `/api/v1/auth/*`. As demais rotas exigem autenticação via JWT (Bearer ou cookie httpOnly).
|
|
|
|
Durante o startup, caso nenhum usuário superadmin exista na coleção, o BFF cria automaticamente um registro utilizando as credenciais definidas em `SUPERADMIN_IDENTIFICADOR` e `SUPERADMIN_SENHA`. Se a senha estiver vazia ou muito longa (> 1024 caracteres), o sistema registra um aviso no log e gera uma senha temporária aleatória.
|
|
|
|
### Categorias
|
|
```bash
|
|
curl -X GET "http://localhost:8000/api/v1/categorias?page=1&per_page=20"
|
|
curl -X POST "http://localhost:8000/api/v1/categorias" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"nome": "Dermocosméticos"}'
|
|
curl -X GET "http://localhost:8000/api/v1/categorias/cat_1"
|
|
curl -X PATCH "http://localhost:8000/api/v1/categorias/cat_1" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"nome": "Dermocosméticos premium"}'
|
|
curl -X DELETE "http://localhost:8000/api/v1/categorias/cat_1"
|
|
```
|
|
|
|
### Subcategorias
|
|
```bash
|
|
curl -X GET "http://localhost:8000/api/v1/subcategorias?page=1&per_page=20"
|
|
curl -X POST "http://localhost:8000/api/v1/subcategorias" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"nome": "Suplementos", "categoriaId": "cat_1"}'
|
|
curl -X GET "http://localhost:8000/api/v1/subcategorias/sub_1"
|
|
curl -X PATCH "http://localhost:8000/api/v1/subcategorias/sub_1" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"nome": "Suplementos esportivos"}'
|
|
curl -X DELETE "http://localhost:8000/api/v1/subcategorias/sub_1"
|
|
```
|
|
|
|
### Usuarios
|
|
```bash
|
|
curl -X GET "http://localhost:8000/api/v1/usuarios?page=1&per_page=20"
|
|
curl -X POST "http://localhost:8000/api/v1/usuarios" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"identificador": "admin@saveinmed.com", "senha": "senha123", "ativo": true, "superadmin": true, "telefone": "11987654321"}'
|
|
curl -X GET "http://localhost:8000/api/v1/usuarios/user_1"
|
|
curl -X PATCH "http://localhost:8000/api/v1/usuarios/user_1" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"ativo": false}'
|
|
curl -X DELETE "http://localhost:8000/api/v1/usuarios/user_1"
|
|
```
|
|
|
|
### Empresas
|
|
```bash
|
|
curl -X GET "http://localhost:8000/api/v1/empresas?page=1&per_page=20"
|
|
curl -X POST "http://localhost:8000/api/v1/empresas" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"cnpj": "12.345.678/0001-00", "razaoSocial": "Clínica Alfa LTDA", "nomeFantasia": "Clínica Alfa"}'
|
|
curl -X GET "http://localhost:8000/api/v1/empresas/emp_1"
|
|
curl -X PATCH "http://localhost:8000/api/v1/empresas/emp_1" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"nome-fantasia": "Clínica Alfa Centro",
|
|
"telefone": "62999999999",
|
|
"email": "contato@clinicaalfa.com"
|
|
}'
|
|
curl -X DELETE "http://localhost:8000/api/v1/empresas/emp_1"
|
|
```
|
|
|
|
### Enderecos
|
|
```bash
|
|
curl -X GET "http://localhost:8000/api/v1/enderecos?page=1&per_page=20"
|
|
curl -X POST "http://localhost:8000/api/v1/enderecos" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"titulo": "Matriz", "cep": "12345-678", "cidade": "São Paulo", "estado": "SP"}'
|
|
curl -X GET "http://localhost:8000/api/v1/enderecos/end_1"
|
|
curl -X PATCH "http://localhost:8000/api/v1/enderecos/end_1" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"numero": "456"}'
|
|
curl -X DELETE "http://localhost:8000/api/v1/enderecos/end_1"
|
|
```
|
|
|
|
## Estrutura do projeto
|
|
```
|
|
.
|
|
├─ src/
|
|
│ ├─ __init__.py
|
|
│ ├─ main.py
|
|
│ ├─ app/
|
|
│ │ ├─ __init__.py
|
|
│ │ ├─ main.py
|
|
│ │ └─ api/
|
|
│ │ ├─ __init__.py
|
|
│ │ └─ v1/
|
|
│ │ ├─ __init__.py
|
|
│ │ └─ routes.py
|
|
│ ├─ core/
|
|
│ │ ├─ __init__.py
|
|
│ │ ├─ appwrite_client.py
|
|
│ │ ├─ config.py
|
|
│ │ ├─ deps.py
|
|
│ │ └─ security.py
|
|
│ └─ modules/
|
|
│ ├─ __init__.py
|
|
│ ├─ categorias/
|
|
│ │ ├─ __init__.py
|
|
│ │ ├─ router.py
|
|
│ │ ├─ schemas.py
|
|
│ │ └─ service.py
|
|
│ ├─ carrinhos/
|
|
│ │ ├─ __init__.py
|
|
│ │ ├─ router.py
|
|
│ │ ├─ schemas.py
|
|
│ │ └─ service.py
|
|
│ ├─ pedidos/
|
|
│ │ ├─ __init__.py
|
|
│ │ ├─ router.py
|
|
│ │ ├─ schemas.py
|
|
│ │ └─ service.py
|
|
│ └─ produtos_catalogo/
|
|
│ ├─ __init__.py
|
|
│ ├─ router.py
|
|
│ ├─ schemas.py
|
|
│ └─ service.py
|
|
├─ requirements.txt
|
|
├─ Dockerfile
|
|
├─ README.md
|
|
└─ .gitignore
|
|
```
|
|
|
|
## Próximos passos
|
|
- Criar as relações no Appwrite para os campos `usuarios`, `pedidos`, `comprador`, `vendedor` e `pagamentos` das coleções de carrinhos e pedidos. Quando as relações estiverem disponíveis, ajustar o BFF para enviar/receber os objetos de relação em vez de strings.
|
|
|
|
## Licença
|
|
MIT
|