Use HTTPS
Sempre use HTTPS em produção para proteger dados em trânsito.
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 é insegurolocalStorage.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=Strictasync 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çãofunction 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 APIfunction 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 APIconst 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 timezoneconst badTime = "2025-12-10T06:00:00"; // Ambíguo!
// ❌ NÃO exiba UTC diretamente aos usuáriosconsole.log("Agendamento inicia às: 2025-12-10T09:00:00Z"); // Confuso!
// ✅ SEMPRE converta para exibiçãoconst displayTime = formatTimestampForUser("2025-12-10T09:00:00Z");console.log(`Agendamento inicia às: ${displayTime}`); // "10/12/2025, 06:00:00"import timeimport requestsfrom 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 menoresasync 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 loggingimport jsonfrom 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: