197 lines
6.0 KiB
JavaScript
197 lines
6.0 KiB
JavaScript
/**
|
|
* ============================================================
|
|
* SERVIÇO DE STORAGE — MinIO S3-Compatible (Self-Hosted)
|
|
* Substitui todas as chamadas supabase.storage do sistema
|
|
* ============================================================
|
|
*/
|
|
import { S3Client, PutObjectCommand, GetObjectCommand, ListBucketsCommand, ListObjectsV2Command, DeleteObjectCommand, CreateBucketCommand, HeadBucketCommand } from '@aws-sdk/client-s3';
|
|
|
|
const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'minio';
|
|
const MINIO_PORT = process.env.MINIO_PORT || '9000';
|
|
const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || 'minioadmin';
|
|
const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY || 'MiniO2026!Seguro';
|
|
const MINIO_PUBLIC_URL = process.env.MINIO_PUBLIC_URL || 'http://localhost:9000';
|
|
|
|
// Cliente S3 apontando para o MinIO interno
|
|
const s3Client = new S3Client({
|
|
endpoint: `http://${MINIO_ENDPOINT}:${MINIO_PORT}`,
|
|
region: 'us-east-1', // MinIO ignora, mas o SDK exige
|
|
credentials: {
|
|
accessKeyId: MINIO_ACCESS_KEY,
|
|
secretAccessKey: MINIO_SECRET_KEY,
|
|
},
|
|
forcePathStyle: true, // Obrigatório para MinIO
|
|
});
|
|
|
|
/**
|
|
* Garante que o bucket existe, criando-o se necessário
|
|
*/
|
|
async function ensureBucketExists(bucket) {
|
|
try {
|
|
await s3Client.send(new HeadBucketCommand({ Bucket: bucket }));
|
|
} catch (err) {
|
|
// Se o erro for 404 (não encontrado), cria o bucket
|
|
try {
|
|
await s3Client.send(new CreateBucketCommand({ Bucket: bucket }));
|
|
console.log(`[Storage] Bucket criado com sucesso: ${bucket}`);
|
|
} catch (createErr) {
|
|
console.error(`[Storage] Erro ao criar bucket ${bucket}:`, createErr.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload de arquivo para o MinIO
|
|
*/
|
|
export async function uploadFile(bucket, fileName, fileBuffer, contentType) {
|
|
// Garantir que o bucket existe antes do upload
|
|
await ensureBucketExists(bucket);
|
|
|
|
const command = new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: fileName,
|
|
Body: fileBuffer,
|
|
ContentType: contentType,
|
|
});
|
|
|
|
await s3Client.send(command);
|
|
|
|
// Retorna URL relativa que será servida pelo proxy do backend
|
|
return `/storage/${bucket}/${fileName}`;
|
|
}
|
|
|
|
/**
|
|
* Gera a URL pública de um arquivo existente
|
|
*/
|
|
export function getPublicUrl(bucket, fileName) {
|
|
return `${MINIO_PUBLIC_URL}/${bucket}/${fileName}`;
|
|
}
|
|
|
|
/**
|
|
* Upload de logo da escola
|
|
*/
|
|
export async function uploadLogo(fileBuffer, contentType) {
|
|
const ext = contentType.includes('webp') ? 'webp' : 'png';
|
|
const fileName = `logo_${Date.now()}.${ext}`;
|
|
return uploadFile('logos', fileName, fileBuffer, contentType);
|
|
}
|
|
|
|
/**
|
|
* Upload de foto de aluno
|
|
*/
|
|
export async function uploadStudentPhoto(fileBuffer, contentType) {
|
|
const ext = contentType.includes('webp') ? 'webp' : contentType.split('/')[1] || 'jpg';
|
|
const fileName = `student_${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`;
|
|
return uploadFile('fotos-alunos', fileName, fileBuffer, contentType);
|
|
}
|
|
|
|
/**
|
|
* Upload de carnê PDF
|
|
*/
|
|
export async function uploadCarne(fileName, pdfBuffer) {
|
|
return uploadFile('carnes', fileName, pdfBuffer, 'application/pdf');
|
|
}
|
|
|
|
/**
|
|
* Upload de recibo PDF
|
|
*/
|
|
export async function uploadReceipt(fileName, pdfBuffer) {
|
|
return uploadFile('recibos', fileName, pdfBuffer, 'application/pdf');
|
|
}
|
|
|
|
/**
|
|
* Upload de imagem de prova
|
|
*/
|
|
export async function uploadExamImage(fileBuffer, contentType) {
|
|
const ext = contentType.split('/')[1] || 'webp';
|
|
const fileName = `exam_${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`;
|
|
return uploadFile('exames', fileName, fileBuffer, contentType);
|
|
}
|
|
|
|
/**
|
|
* Upload de atestado/justificativa
|
|
*/
|
|
export async function uploadAtestado(fileBuffer, contentType) {
|
|
const ext = contentType.split('/')[1] || 'jpg';
|
|
const fileName = `atestado_${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`;
|
|
return uploadFile('atestados', fileName, fileBuffer, contentType);
|
|
}
|
|
|
|
export async function getMinioStats() {
|
|
try {
|
|
const data = await s3Client.send(new ListBucketsCommand({}));
|
|
const buckets = data.Buckets || [];
|
|
let totalSize = 0;
|
|
let totalItems = 0;
|
|
|
|
const bucketsInfo = await Promise.all(buckets.map(async (bucket) => {
|
|
let bucketSize = 0;
|
|
let bucketItems = 0;
|
|
try {
|
|
const objects = await s3Client.send(new ListObjectsV2Command({ Bucket: bucket.Name }));
|
|
if (objects.Contents) {
|
|
bucketItems = objects.Contents.length;
|
|
bucketSize = objects.Contents.reduce((acc, curr) => acc + (curr.Size || 0), 0);
|
|
}
|
|
} catch (e) {} // Ignorar buckets inacessíveis
|
|
|
|
totalSize += bucketSize;
|
|
totalItems += bucketItems;
|
|
|
|
return {
|
|
name: bucket.Name,
|
|
items: bucketItems,
|
|
sizeMB: (bucketSize / (1024 * 1024)).toFixed(2)
|
|
};
|
|
}));
|
|
|
|
return {
|
|
bucketCount: buckets.length,
|
|
totalItems,
|
|
totalSizeMB: (totalSize / (1024 * 1024)).toFixed(2),
|
|
buckets: bucketsInfo
|
|
};
|
|
} catch (error) {
|
|
console.error('Erro ao buscar stats do MinIO:', error);
|
|
return { error: true, message: error.message };
|
|
}
|
|
}
|
|
|
|
export async function getBucketObjects(bucketName) {
|
|
try {
|
|
const data = await s3Client.send(new ListObjectsV2Command({ Bucket: bucketName }));
|
|
if (!data.Contents) return [];
|
|
|
|
return data.Contents.map(obj => ({
|
|
key: obj.Key,
|
|
size: obj.Size,
|
|
lastModified: obj.LastModified,
|
|
url: `/storage/${bucketName}/${obj.Key}`
|
|
}));
|
|
} catch (error) {
|
|
console.error(`Erro ao listar objetos do bucket ${bucketName}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function deleteMinioObject(bucketName, key) {
|
|
try {
|
|
const command = new DeleteObjectCommand({ Bucket: bucketName, Key: key });
|
|
await s3Client.send(command);
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Erro ao deletar objeto ${key} do bucket ${bucketName}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export { s3Client };
|
|
|
|
// Inicialização proativa de buckets essenciais
|
|
ensureBucketExists('recibos');
|
|
ensureBucketExists('carnes');
|
|
ensureBucketExists('logos');
|
|
ensureBucketExists('fotos-alunos');
|
|
ensureBucketExists('atestados');
|
|
ensureBucketExists('exames');
|