/** * Serviço de Avatars (Frontend) */ import axios from "axios"; import { API_CONFIG } from "../api/config"; import type { UploadAvatarInput, UploadAvatarResponse, DeleteAvatarInput, GetAvatarUrlInput, } from "./types"; class AvatarService { private readonly SUPABASE_URL = API_CONFIG.SUPABASE_URL; private readonly STORAGE_URL = `${this.SUPABASE_URL}/storage/v1/object`; private readonly BUCKET_NAME = "avatars"; /** * Cria uma instância limpa do axios sem baseURL * Para evitar conflitos com configurações globais */ private createAxiosInstance() { return axios.create({ // NÃO definir baseURL aqui - usaremos URL completa timeout: 30000, maxContentLength: 2 * 1024 * 1024, // 2MB maxBodyLength: 2 * 1024 * 1024, // 2MB }); } /** * Faz upload de avatar do usuário */ async upload(data: UploadAvatarInput): Promise { try { const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN); if (!token) { throw new Error("Token de autenticação não encontrado"); } // Determina a extensão do arquivo const ext = data.file.name.split(".").pop()?.toLowerCase() || "jpg"; const filePath = `${data.userId}/avatar.${ext}`; // Cria FormData para o upload const formData = new FormData(); formData.append("file", data.file); // URL COMPLETA (sem baseURL do axios) const uploadUrl = `${this.STORAGE_URL}/${this.BUCKET_NAME}/${filePath}`; console.log("[AvatarService] 🚀 Upload iniciado:", { uploadUrl, STORAGE_URL: this.STORAGE_URL, BUCKET_NAME: this.BUCKET_NAME, filePath, userId: data.userId, fileName: data.file.name, fileSize: data.file.size, fileType: data.file.type, token: token ? `${token.substring(0, 20)}...` : "null", }); // Cria instância limpa do axios const axiosInstance = this.createAxiosInstance(); console.log("[AvatarService] 🔍 Verificando URL antes do POST:"); console.log(" - URL completa:", uploadUrl); console.log(" - Deve começar com:", this.SUPABASE_URL); console.log(" - Deve conter: /storage/v1/object/avatars/"); // Upload usando Supabase Storage API // Importante: NÃO definir Content-Type manualmente const response = await axiosInstance.post(uploadUrl, formData, { headers: { Authorization: `Bearer ${token}`, apikey: API_CONFIG.SUPABASE_ANON_KEY, "x-upsert": "true", }, }); console.log("[AvatarService] ✅ Upload bem-sucedido:", response.data); console.log("[AvatarService] 📍 URL real usada:", response.config?.url); // Retorna a URL pública const publicUrl = this.getPublicUrl({ userId: data.userId, ext: ext as "jpg" | "png" | "webp", }); return { Key: publicUrl, }; } catch (error) { console.error("❌ [AvatarService] Erro ao fazer upload:", error); if (axios.isAxiosError(error)) { console.error("📋 Detalhes do erro:", { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, message: error.message, requestUrl: error.config?.url, requestMethod: error.config?.method, headers: error.config?.headers, }); console.error("🔍 URL que foi enviada:", error.config?.url); console.error( "🔍 URL esperada:", `${this.STORAGE_URL}/${this.BUCKET_NAME}/{user_id}/avatar.{ext}` ); // Mensagens de erro mais específicas if (error.response?.status === 400) { console.error( "💡 Erro 400: Verifique se o bucket 'avatars' existe e está configurado corretamente" ); console.error( " OU: Verifique se a URL está correta (deve ter /storage/v1/object/avatars/)" ); } else if (error.response?.status === 401) { console.error("💡 Erro 401: Token inválido ou expirado"); } else if (error.response?.status === 403) { console.error( "💡 Erro 403: Sem permissão. Verifique as políticas RLS do Storage" ); } } throw error; } } /** * Remove avatar do usuário (sobrescreve com imagem vazia ou remove do perfil) */ async delete(_data: DeleteAvatarInput): Promise { try { // Não há endpoint de delete, então apenas removemos a referência do perfil // O upload futuro irá sobrescrever a imagem antiga console.log( "Avatar será removido do perfil. Upload futuro sobrescreverá a imagem." ); } catch (error) { console.error("Erro ao deletar avatar:", error); throw error; } } /** * Retorna a URL pública do avatar * Não precisa de autenticação pois é endpoint público */ getPublicUrl(data: GetAvatarUrlInput): string { return `${this.STORAGE_URL}/${this.BUCKET_NAME}/${data.userId}/avatar.${data.ext}`; } } export const avatarService = new AvatarService();