/** * 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"; /** * 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); console.log("[AvatarService] Upload:", { url: `${this.STORAGE_URL}/${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", }); // Upload usando Supabase Storage API // x-upsert: true permite sobrescrever arquivos existentes // Importante: NÃO definir Content-Type manualmente, deixar o axios/navegador // definir automaticamente com o boundary correto para multipart/form-data const response = await axios.post( `${this.STORAGE_URL}/${this.BUCKET_NAME}/${filePath}`, formData, { headers: { "Authorization": `Bearer ${token}`, "x-upsert": "true", }, } ); console.log("[AvatarService] Upload response:", response.data); // 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("Erro ao fazer upload do avatar:", error); if (axios.isAxiosError(error)) { console.error("Detalhes do erro:", { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, url: error.config?.url, }); } 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();