111 lines
3.3 KiB
TypeScript

/**
* 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<UploadAvatarResponse> {
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<void> {
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();