Initial SeConectai briefing system
This commit is contained in:
+134
@@ -0,0 +1,134 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import { mkdir, unlink, writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const allowedExtensions = new Set([
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".webp",
|
||||
".svg",
|
||||
".pdf",
|
||||
".docx",
|
||||
".doc",
|
||||
".xlsx",
|
||||
".xls",
|
||||
".pptx",
|
||||
".zip",
|
||||
".mp4",
|
||||
".mov"
|
||||
]);
|
||||
|
||||
const allowedMimes = new Set([
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"application/pdf",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"application/zip",
|
||||
"video/mp4",
|
||||
"video/quicktime"
|
||||
]);
|
||||
|
||||
export function getUploadDir() {
|
||||
return process.env.UPLOAD_DIR ?? path.join(process.cwd(), "storage", "uploads");
|
||||
}
|
||||
|
||||
export function getUploadMaxBytes() {
|
||||
const maxMb = Number(process.env.UPLOAD_MAX_MB ?? 25);
|
||||
return maxMb * 1024 * 1024;
|
||||
}
|
||||
|
||||
function hasMagicBytes(buffer: Buffer, mime: string, extension: string) {
|
||||
if (mime === "image/png") {
|
||||
return buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
|
||||
}
|
||||
|
||||
if (mime === "image/jpeg") {
|
||||
return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff;
|
||||
}
|
||||
|
||||
if (mime === "image/webp") {
|
||||
return buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP";
|
||||
}
|
||||
|
||||
if (mime === "application/pdf") {
|
||||
return buffer.subarray(0, 4).toString("ascii") === "%PDF";
|
||||
}
|
||||
|
||||
if (mime === "video/mp4" || mime === "video/quicktime") {
|
||||
return buffer.subarray(4, 8).toString("ascii") === "ftyp";
|
||||
}
|
||||
|
||||
if (extension === ".zip" || extension === ".docx" || extension === ".xlsx" || extension === ".pptx") {
|
||||
return buffer[0] === 0x50 && buffer[1] === 0x4b;
|
||||
}
|
||||
|
||||
if (extension === ".doc" || extension === ".xls") {
|
||||
return buffer[0] === 0xd0 && buffer[1] === 0xcf && buffer[2] === 0x11 && buffer[3] === 0xe0;
|
||||
}
|
||||
|
||||
if (mime === "image/svg+xml") {
|
||||
const start = buffer.subarray(0, 512).toString("utf8").trimStart().toLowerCase();
|
||||
return start.startsWith("<svg") || start.startsWith("<?xml");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function validateAndStoreUpload(file: File) {
|
||||
const originalName = file.name;
|
||||
const extension = path.extname(originalName).toLowerCase();
|
||||
const mime = file.type;
|
||||
const maxBytes = getUploadMaxBytes();
|
||||
|
||||
if (!allowedExtensions.has(extension)) {
|
||||
throw new Error("Tipo de arquivo nao permitido.");
|
||||
}
|
||||
|
||||
if (!allowedMimes.has(mime)) {
|
||||
throw new Error("MIME do arquivo nao permitido.");
|
||||
}
|
||||
|
||||
if (file.size <= 0) {
|
||||
throw new Error("Arquivo vazio.");
|
||||
}
|
||||
|
||||
if (file.size > maxBytes) {
|
||||
throw new Error(`Arquivo maior que o limite de ${Math.round(maxBytes / 1024 / 1024)} MB.`);
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
if (!hasMagicBytes(buffer, mime, extension)) {
|
||||
throw new Error("Conteudo do arquivo nao confere com o tipo informado.");
|
||||
}
|
||||
|
||||
const uploadDir = getUploadDir();
|
||||
await mkdir(uploadDir, { recursive: true });
|
||||
|
||||
const storedName = `${randomUUID()}${extension}`;
|
||||
const storedPath = path.join(uploadDir, storedName);
|
||||
|
||||
await writeFile(storedPath, buffer);
|
||||
|
||||
return {
|
||||
originalName,
|
||||
storedPath,
|
||||
mime,
|
||||
size: file.size
|
||||
};
|
||||
}
|
||||
|
||||
export async function removeStoredFile(filePath: string) {
|
||||
try {
|
||||
await unlink(filePath);
|
||||
} catch {
|
||||
// A ausencia do arquivo nao deve impedir a remocao do registro.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user