Pular para o conteúdo

Boas Práticas

Use HTTPS

Sempre use HTTPS em produção para proteger dados em trânsito.

Proteja seus Tokens

Nunca exponha tokens JWT em código client-side ou repositórios públicos.

Senhas Fortes

Use senhas com mínimo de 8 caracteres, combinando letras, números e símbolos.

Rotação de Chaves

Rotacione a SECRET_KEY regularmente em produção.


// ❌ NÃO faça isso - armazenar em localStorage é inseguro
localStorage.setItem('token', accessToken);
// ✅ Prefira usar httpOnly cookies ou memória da aplicação
// O backend pode configurar cookies seguros:
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict
async function fetchWithAuth(url, options = {}) {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${getToken()}`,
},
});
// Se token expirou, tente renovar
if (response.status === 401) {
const newToken = await refreshToken();
if (newToken) {
response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newToken}`,
},
});
}
}
return response;
}

Todos os timestamps na API AeraPlus são armazenados e retornados em UTC (formato ISO 8601 com sufixo Z).

// ✅ Converta timestamps UTC para o timezone local do usuário para exibição
function formatTimestampForUser(utcTimestamp) {
const date = new Date(utcTimestamp); // "2025-11-21T10:30:00Z"
// Exibe no timezone local do usuário
return date.toLocaleString('pt-BR', {
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
}
// ✅ Converta hora local do usuário para UTC antes de enviar para a API
function createScheduleTime(localDateTime) {
const date = new Date(localDateTime);
return date.toISOString(); // Retorna UTC com sufixo Z
}
// Usuário seleciona "6:00 AM" no seu timezone local (ex: America/Fortaleza, UTC-3)
const userLocalTime = "2025-12-10T06:00:00"; // Sem informação de timezone ainda
// Converte para UTC antes de enviar para a API
const utcTime = new Date(userLocalTime).toISOString(); // "2025-12-10T09:00:00Z"
await fetch('/api/v1/control-points/cp-uuid/schedules', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'ON',
start_time: utcTime, // Sempre envie UTC para a API
repeat_pattern: 'DAILY',
}),
});
// ❌ NÃO envie hora local sem conversão de timezone
const badTime = "2025-12-10T06:00:00"; // Ambíguo!
// ❌ NÃO exiba UTC diretamente aos usuários
console.log("Agendamento inicia às: 2025-12-10T09:00:00Z"); // Confuso!
// ✅ SEMPRE converta para exibição
const displayTime = formatTimestampForUser("2025-12-10T09:00:00Z");
console.log(`Agendamento inicia às: ${displayTime}`); // "10/12/2025, 06:00:00"

import time
import requests
from functools import wraps
def retry_with_backoff(max_retries=3, backoff_factor=2):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
wait_time = backoff_factor ** attempt
print(f"Tentativa {attempt + 1} falhou. Aguardando {wait_time}s...")
time.sleep(wait_time)
return wrapper
return decorator
@retry_with_backoff(max_retries=3)
def get_farm(farm_id: str, token: str):
response = requests.get(
f"http://localhost:8000/api/v1/farms/{farm_id}",
headers={"Authorization": f"Bearer {token}"},
timeout=10
)
response.raise_for_status()
return response.json()
async function handleApiResponse(response) {
if (response.ok) {
return await response.json();
}
const error = await response.json().catch(() => ({}));
switch (response.status) {
case 400:
throw new ValidationError(error.detail || 'Dados inválidos');
case 401:
throw new AuthError('Sessão expirada. Faça login novamente.');
case 403:
throw new ForbiddenError('Você não tem permissão para esta ação.');
case 404:
throw new NotFoundError(error.detail || 'Recurso não encontrado');
case 422:
throw new ValidationError(formatValidationErrors(error.detail));
case 500:
throw new ServerError('Erro interno. Tente novamente mais tarde.');
default:
throw new ApiError(`Erro inesperado: ${response.status}`);
}
}

// Busque dados em páginas menores
async function getAllFarms(token) {
let page = 1;
let allFarms = [];
let hasMore = true;
while (hasMore) {
const response = await fetch(
`http://localhost:8000/api/v1/farms/?page=${page}&limit=50`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
const farms = await response.json();
allFarms = [...allFarms, ...farms];
hasMore = farms.length === 50;
page++;
}
return allFarms;
}
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutos
async function getCachedFarm(farmId, token) {
const cacheKey = `farm:${farmId}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const response = await fetch(
`http://localhost:8000/api/v1/farms/${farmId}`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
const farm = await response.json();
cache.set(cacheKey, { data: farm, timestamp: Date.now() });
return farm;
}

interface CreateFarmData {
name: string;
description?: string;
lat?: string;
lng?: string;
}
function validateFarmData(data: CreateFarmData): string[] {
const errors: string[] = [];
if (!data.name || data.name.trim().length === 0) {
errors.push('Nome é obrigatório');
}
if (data.name && data.name.length > 100) {
errors.push('Nome deve ter no máximo 100 caracteres');
}
if (data.lat && !isValidLatitude(data.lat)) {
errors.push('Latitude inválida');
}
if (data.lng && !isValidLongitude(data.lng)) {
errors.push('Longitude inválida');
}
return errors;
}
function isValidLatitude(lat: string): boolean {
const num = parseFloat(lat);
return !isNaN(num) && num >= -90 && num <= 90;
}
function isValidLongitude(lng: string): boolean {
const num = parseFloat(lng);
return !isNaN(num) && num >= -180 && num <= 180;
}

import logging
import json
from datetime import datetime
class ApiLogger:
def __init__(self, name: str):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.INFO)
def log_request(self, method: str, url: str, status: int, duration_ms: float):
self.logger.info(json.dumps({
"timestamp": datetime.utcnow().isoformat(),
"type": "api_request",
"method": method,
"url": url,
"status": status,
"duration_ms": duration_ms
}))
def log_error(self, error: Exception, context: dict = None):
self.logger.error(json.dumps({
"timestamp": datetime.utcnow().isoformat(),
"type": "error",
"error": str(error),
"error_type": type(error).__name__,
"context": context or {}
}))

Antes de ir para produção, verifique:

  • HTTPS configurado e forçado
  • SECRET_KEY única e segura
  • CORS configurado para domínios específicos
  • Logs de erros configurados
  • Backup de banco de dados configurado
  • Monitoramento de uptime
  • Tratamento de erros robusto no cliente
  • Validação de inputs no cliente e servidor
  • Testes automatizados passando
  • Documentação atualizada