feat: Implement Phase 2 cross-cutting services with email and notification enhancements

- 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.
This commit is contained in:
Sathira Sri Sathara
2026-09-03 14:22:34 +05:30
parent 9d3d431416
commit b6b345f245
54 changed files with 593 additions and 1248 deletions
+36 -123
View File
@@ -1,131 +1,44 @@
/**
* Copyright (c) 2026 Niolla
* All rights reserved.
*
* This source code is proprietary and confidential.
* Unauthorized copying, modification, distribution, or use
* of this file, via any medium, is strictly prohibited.
*/
// app/controllers/activity.controller.js
const { uploadToS3, getSignedFileUrl } = require("../utils/s3Upload.utill");
const { log } = require("../utils/consoleLog.utill");
const db = require("../models");
const Upload = db.Upload;
const Assets = db.Assets;
const storage = require("../services/storage/storage.service");
const { validateUpload } = require("../services/storage/file-validation.service");
// Constants
const MAX_IMAGE_SIZE = 3 * 1024 * 1024; // 3MB
const MAX_PDF_SIZE = 5 * 1024 * 1024; // 5MB
const canAccess = (user, upload) => upload.visibility === "PUBLIC" || upload.owner_id === user.id || user.accountType === "super_admin" || (user.accountType === "admin" && (user.permissions || []).includes("media.read"));
const isValidFileType = (mimetype) => {
return mimetype.startsWith("image/") || mimetype === "application/pdf";
};
const isValidFileSize = (mimetype, size) => {
if (mimetype.startsWith("image/")) return size <= MAX_IMAGE_SIZE;
if (mimetype === "application/pdf") return size <= MAX_PDF_SIZE;
return false;
};
exports.uploadFile = async (req, res) => {
exports.uploadFile = async (req, res, next) => {
let objectKey;
try {
// 1. Check file exists
if (!req.file) {
return res.status(400).json({
success: false,
message: "No file uploaded",
});
}
const { mimetype, size, originalname } = req.file;
// 2. Validate file type
if (!isValidFileType(mimetype)) {
return res.status(400).json({
success: false,
message: "Only images and PDFs are allowed",
});
}
// 3. Validate file size
if (!isValidFileSize(mimetype, size)) {
return res.status(400).json({
success: false,
message: mimetype.startsWith("image/")
? "Image too large (max 1MB)"
: "PDF too large (max 5MB)",
});
}
log("File validation passed:", {
mimetype,
size,
originalname,
use_for: req.body.use_for,
});
// 4. Upload to S3
const fileKey = await uploadToS3(req.file, req.body.use_for);
const fileUrl = await getSignedFileUrl(fileKey);
// 5. Save to DB
const uploadData = {
file_path: fileKey,
file_type: mimetype,
file_size: size,
original_name: originalname,
use_for: req.body.use_for || null,
uploaded_by: req.user?.id || null,
};
const newUpload = await Upload.create(uploadData);
newUpload.dataValues.file_url = fileUrl; // Add URL to response
// 6. Response
return res.status(201).json({
success: true,
message: "File uploaded successfully",
data: newUpload,
});
} catch (error) {
console.error("Upload Controller Error:", error);
return res.status(500).json({
success: false,
message: "Failed to upload file",
error: process.env.NODE_ENV === "development" ? error.message : undefined,
});
}
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); }
};
// Get Uploaded File URL
exports.getFileUrl = async (req, res) => {
exports.getFileUrl = async (req, res, next) => {
try {
const { id } = req.params;
const uploadRecord = await Upload.findByPk(id);
if (!uploadRecord) {
return res.status(404).json({
success: false,
message: "File not found",
});
}
const fileUrl = await getSignedFileUrl(uploadRecord.file_path);
return res.status(200).json({
success: true,
message: "File URL retrieved successfully",
data: {
id: uploadRecord.id,
file_url: fileUrl,
},
});
} catch (error) {}
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); }
};