111 lines
3.3 KiB
TypeScript
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();
|