const db = require("../models"); const storage = require("../services/storage/storage.service"); const { validateUpload } = require("../services/storage/file-validation.service"); const BUSINESS_DOCUMENT_PURPOSES = new Set(["BUSINESS_REGISTRATION", "TAX_DOCUMENT", "IDENTITY_DOCUMENT", "OTHER_SUPPORTING_DOCUMENT"]); const canAccess = (user, upload) => upload.visibility === "PUBLIC" || upload.owner_id === user.id || user.accountType === "superadmin" || (user.permissions || []).includes("media.read") || (BUSINESS_DOCUMENT_PURPOSES.has(upload.use_for) && (user.permissions || []).includes("business.applications.review")); exports.uploadFile = async (req, res, next) => { let objectKey; try { const details = validateUpload(req.file); const purpose = String(req.body.use_for || "generic").replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 60); objectKey = storage.createObjectKey({ ownerId: req.user.id, mimeType: details.mimeType, purpose }); await storage.uploadBuffer({ buffer: req.file.buffer, objectKey, mimeType: details.mimeType, checksum: details.checksum }); let record; try { record = await db.Upload.create({ file_path: objectKey, file_type: details.mimeType, file_size: details.size, original_name: req.file.originalname, safe_name: details.safeName, checksum: details.checksum, use_for: purpose, uploaded_by: req.user.id, owner_type: "USER", owner_id: req.user.id, visibility: "PRIVATE", status: "AVAILABLE" }); } catch (error) { await storage.deleteObject(objectKey).catch(() => undefined); throw error; } const expiresIn = Number(process.env.S3_SIGNED_URL_TTL_SECONDS || 900); return res.status(201).json({ success: true, data: { id: record.id, originalName: record.original_name, mimeType: record.file_type, size: record.file_size, purpose: record.use_for, status: record.status, url: await storage.createSignedDownloadUrl(objectKey, expiresIn), expiresIn } }); } catch (error) { return next(error); } }; exports.getFileUrl = async (req, res, next) => { try { const upload = await db.Upload.findByPk(req.params.id); if (!upload || upload.status === "DELETED") return res.status(404).json({ success: false, error: { code: "NOT_FOUND", message: "File not found" } }); if (!canAccess(req.user, upload)) return res.status(403).json({ success: false, error: { code: "FORBIDDEN", message: "Access denied" } }); if (upload.status !== "AVAILABLE") return res.status(409).json({ success: false, error: { code: "FILE_UNAVAILABLE", message: "File is not available" } }); const expiresIn = Number(process.env.S3_SIGNED_URL_TTL_SECONDS || 900); return res.json({ success: true, data: { id: upload.id, url: await storage.createSignedDownloadUrl(upload.file_path, expiresIn), expiresIn } }); } catch (error) { return next(error); } }; exports.deleteFile = async (req, res, next) => { try { const upload = await db.Upload.findByPk(req.params.id); if (!upload || upload.status === "DELETED") return res.status(404).json({ success: false, error: { code: "NOT_FOUND", message: "File not found" } }); const allowed = upload.owner_id === req.user.id || req.user.accountType === "super_admin" || (req.user.permissions || []).includes("media.delete"); if (!allowed) return res.status(403).json({ success: false, error: { code: "FORBIDDEN", message: "Access denied" } }); await upload.update({ status: "DELETED", deletedAt: new Date() }); await storage.deleteObject(upload.file_path).catch(() => undefined); return res.status(204).end(); } catch (error) { return next(error); } };