Os arquivos são enviados para o MinIO (armazenamento de objetos) e retornam uma URL permanente para uso nas mensagens.
Tipos de Mídia Suportados
Imagens
JPEG/JPG (.jpg
, .jpeg
)
PNG (.png
)
GIF (.gif
)
WebP (.webp
)
MIME types: image/*
Vídeos
MP4 (.mp4
)
AVI (.avi
)
MOV (.mov
)
MKV (.mkv
)
MIME types: video/*
Áudios
MP3 (.mp3
)
WAV (.wav
)
OGG (.ogg
)
M4A (.m4a
)
AAC (.aac
)
MIME types: audio/*
Documentos
PDF (.pdf
)
DOC/DOCX (.doc
, .docx
)
XLS/XLSX (.xls
, .xlsx
)
MIME types específicos aceitos
Token de acesso da empresa
Deve ser multipart/form-data
(geralmente configurado automaticamente)
Body (Form Data)
Arquivo a ser enviado (campo do formulário)
curl -X POST https://api.disparador.com/media/upload \
-H "X-Access-Token: seu-access-token" \
-F "file=@/caminho/para/arquivo.jpg"
Response
URL pública permanente do arquivo no MinIO
Tamanho do arquivo em bytes
Nome do bucket no MinIO onde o arquivo foi armazenado
Nome do objeto no MinIO (caminho interno)
200 - Sucesso
400 - Arquivo Vazio
400 - Tipo Não Suportado
401 - Token Inválido
500 - Erro no Upload
{
"url" : "https://minio.disparador.com/company-123/media/2024/01/abc123def456-imagem.jpg" ,
"filename" : "imagem-produto.jpg" ,
"size" : 2458624 ,
"type" : "image/jpeg" ,
"bucket" : "company-123" ,
"objectName" : "media/2024/01/abc123def456-imagem.jpg"
}
function uploadWithProgress ( file , accessToken , onProgress ) {
return new Promise (( resolve , reject ) => {
const xhr = new XMLHttpRequest ();
const formData = new FormData ();
formData . append ( 'file' , file );
// Monitorar progresso
xhr . upload . addEventListener ( 'progress' , ( e ) => {
if ( e . lengthComputable ) {
const percentComplete = ( e . loaded / e . total ) * 100 ;
onProgress ( percentComplete );
}
});
// Manipular resposta
xhr . addEventListener ( 'load' , () => {
if ( xhr . status === 200 ) {
resolve ( JSON . parse ( xhr . responseText ));
} else {
reject ( new Error ( `Upload falhou: ${ xhr . status } ` ));
}
});
xhr . addEventListener ( 'error' , () => {
reject ( new Error ( 'Erro de rede no upload' ));
});
xhr . open ( 'POST' , 'https://api.disparador.com/media/upload' );
xhr . setRequestHeader ( 'X-Access-Token' , accessToken );
xhr . send ( formData );
});
}
// Uso
const progressBar = document . getElementById ( 'progress' );
uploadWithProgress ( file , 'seu-token' , ( progress ) => {
console . log ( `Upload: ${ progress . toFixed ( 0 ) } %` );
progressBar . style . width = ` ${ progress } %` ;
}). then ( result => {
console . log ( 'Upload completo:' , result );
}). catch ( error => {
console . error ( 'Erro:' , error );
});
Validação Prévia
function validateFile ( file ) {
const MAX_SIZE = 100 * 1024 * 1024 ; // 100MB
// Validações básicas
const validations = {
size: {
valid: file . size <= MAX_SIZE ,
message: `Arquivo muito grande (máximo ${ MAX_SIZE / 1024 / 1024 } MB)`
},
type: {
valid: isValidFileType ( file . type ),
message: 'Tipo de arquivo não suportado'
}
};
// Verificar todas as validações
for ( const [ key , validation ] of Object . entries ( validations )) {
if ( ! validation . valid ) {
throw new Error ( validation . message );
}
}
return true ;
}
function isValidFileType ( mimeType ) {
const allowedTypes = [
'image/' , 'video/' , 'audio/' ,
'application/pdf' ,
'application/msword' ,
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ,
'application/vnd.ms-excel' ,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
return allowedTypes . some ( type => mimeType . startsWith ( type ) || mimeType === type );
}
// Uso
try {
validateFile ( file );
const result = await uploadMedia ( file , accessToken );
} catch ( error ) {
alert ( error . message );
}
Upload em Lote
async function uploadMultipleFiles ( files , accessToken ) {
const results = [];
const errors = [];
// Upload sequencial para evitar sobrecarga
for ( const file of files ) {
try {
console . log ( `Enviando ${ file . name } ...` );
const result = await uploadMedia ( file , accessToken );
results . push ({
file: file . name ,
success: true ,
url: result . url
});
} catch ( error ) {
errors . push ({
file: file . name ,
success: false ,
error: error . message
});
}
}
return { results , errors };
}
// Uso
const files = Array . from ( document . getElementById ( 'files' ). files );
const { results , errors } = await uploadMultipleFiles ( files , 'seu-token' );
console . log ( `Sucesso: ${ results . length } ` );
console . log ( `Erros: ${ errors . length } ` );
// Fazer upload e criar campanha com a mídia
async function createCampaignWithMedia ( campaignData , mediaFile , accessToken ) {
// 1. Upload da mídia
const mediaResult = await uploadMedia ( mediaFile , accessToken );
// 2. Determinar tipo de mídia
const mediaType = mediaResult . type . split ( '/' )[ 0 ]; // image, video, audio
// 3. Criar campanha com a URL da mídia
const campaign = {
... campaignData ,
mediaUrl: mediaResult . url ,
mediaType: mediaType ,
fileName: mediaResult . filename
};
const response = await fetch ( 'https://api.disparador.com/api/campaigns' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Access-Token' : accessToken
},
body: JSON . stringify ( campaign )
});
return response . json ();
}
Observações
Armazenamento MinIO:
Os arquivos são armazenados no MinIO (S3-compatible)
URLs são permanentes e públicas
Organização automática por empresa e data
Nomes de arquivo são únicos (UUID + nome original)
Limites e Segurança:
Validação de tipo MIME no servidor
Arquivos são organizados por empresa (isolamento)
Considere implementar limites de quota por empresa
URLs públicas - não envie arquivos sensíveis
Compatibilidade
O endpoint /media/files/{filename}
ainda existe para compatibilidade com arquivos antigos armazenados localmente. Novos uploads são direcionados ao MinIO.