b6b345f245
- Refactor email verification and password reset utilities to use new email service. - Introduce email delivery queue and notification delivery model for better tracking. - Enhance file validation and storage services for improved security and ownership management. - Add cron job for cleaning inactive notifications with retention policy. - Update document worker to handle document generation and storage more efficiently. - Implement logging improvements in activity and log workers. - Create comprehensive documentation for new API endpoints and services. - Add unit tests for file validation and notification policies to ensure robustness.
45 lines
3.3 KiB
JavaScript
45 lines
3.3 KiB
JavaScript
const db = require("../models");
|
|
const storage = require("../services/storage/storage.service");
|
|
const { validateUpload } = require("../services/storage/file-validation.service");
|
|
|
|
const canAccess = (user, upload) => upload.visibility === "PUBLIC" || upload.owner_id === user.id || user.accountType === "super_admin" || (user.accountType === "admin" && (user.permissions || []).includes("media.read"));
|
|
|
|
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); }
|
|
};
|