/** * 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/generateDocument.controller.js const fs = require("fs"); const path = require("path"); const documentQueue = require("../queues/document.queue"); const { createDocumentData } = require("../utils/document.utill"); const { generateId, generateDocumentReferenceNo, } = require("../utils/idGen.util"); const { GetObjectCommand, DeleteObjectCommand } = require("@aws-sdk/client-s3"); const {log} = require("../utils/consoleLog.utill"); const s3 = require("../config/s3.config"); const db = require("../models"); const Document = db.Document; const DocumentType = db.DocumentType; const normalizeDocumentData = (value) => { if (!value) { return {}; } if (typeof value === "string") { try { return JSON.parse(value); } catch (error) { return {}; } } return value; }; /** * Get available document types */ exports.getAvailableDocumentTypes = async (req, res) => { try { const doc_types = await DocumentType.findAll({ attributes: ["doc_type_name"], }); const types = doc_types.map((t) => t.doc_type_name); return res.json({ success: true, availableDocumentTypes: types, note: "Use these document type names in your requests (case-insensitive)", }); } catch (error) { log("Error fetching document types:", error.message); return res.status(500).json({ success: false, message: error.message, }); } }; // Get Saved documents exports.getSavedDocuments = async (req, res) => { try { const { documentType } = req.body; if (!documentType) { return res.status(400).json({ success: false, message: "documentType query parameter is required", }); } // Fetch saved documents based on documentType const savedDocuments = await Document.findAll({ where: { doc_type: documentType.toUpperCase() }, order: [["createdAt", "DESC"]], exclude: ["data"], }); // extract only necessary fields to return const formattedDocuments = savedDocuments.map((doc) => ({ doc_id: doc.doc_id, reference_no: doc.reference_no, doc_type: doc.doc_type, status: doc.status, createdAt: doc.createdAt, updatedAt: doc.updatedAt, })); return res.json({ success: true, savedDocuments: formattedDocuments, }); } catch (error) { log("Error fetching saved documents:", error.message); return res.status(500).json({ success: false, message: error.message, }); } }; // Get specific document data by doc_id exports.getDocumentData = async (req, res) => { try { const { docId } = req.params; if (!docId) { return res.status(400).json({ success: false, message: "docId parameter is required", }); } const document = await Document.findOne({ where: { doc_id: docId }, }); if (!document) { return res.status(404).json({ success: false, message: "Document not found", }); } return res.json({ success: true, document: { doc_id: document.doc_id, reference_no: document.reference_no, doc_type: document.doc_type, data: document.data, status: document.status, createdAt: document.createdAt, updatedAt: document.updatedAt, }, }); } catch (error) { log("Error fetching document data:", error.message); return res.status(500).json({ success: false, message: error.message, }); } } exports.generateReferenceNo = async (req, res) => { try { const { documentType } = req.params; if (!documentType) { return res.status(400).json({ success: false, message: "documentType is required", }); } // Generate reference number const reference_no = await generateDocumentReferenceNo(documentType); return res.json({ success: true, reference_no, }); } catch (error) { log("Error generating reference number:", error.message); return res.status(500).json({ success: false, message: error.message, }); } }; exports.generateDraftDocument = async (req, res) => { try { const { document } = req.body; const documentData = normalizeDocumentData(req.body.documentData || req.body.data || req.body); // Validation if (!document || !documentData) { return res.status(400).json({ success: false, message: "document and documentData are required", }); } let documentDetails; if (document !== "PRECOST") { // Save document details before generating documentDetails = await createDocumentData( document, documentData, "DRAFT", ); } else { return res.status(400).json({ success: false, message: "Invalid document type, This document type is not allowed to be generated as draft", }); } return res.status(201).json({ success: true, message: "Draft document created successfully", documentDetails, }); } catch (error) { log("Error generating draft document:", error.message); return res.status(500).json({ success: false, message: error.message, }); } }; /** * Generate document asynchronously * Returns jobId immediately */ exports.generateDocument = async (req, res) => { try { const { document, documentType } = req.body; const documentData = normalizeDocumentData(req.body.documentData || req.body.data || req.body); // Validation if (!document || !documentType || !documentData) { return res.status(400).json({ success: false, message: "document, documentType, and documentData are required", }); } console.log( `📨 generateDocument request: document="${document}", documentType="${documentType}"`, ); if (document !== "PRECOST") { // Save document details before generating // If doc_id exists, finalize (update existing); otherwise create as DRAFT const status = documentData.doc_id ? "FINAL" : "DRAFT"; await createDocumentData(document, documentData, status); } // Add job to queue const job = await documentQueue.add("generate-document", { document, documentType, data: documentData, }); log( `📋 Document generation job queued: ${job.id} (document="${document}", type="${documentType}")`, ); return res.status(202).json({ success: true, message: "Document generation started", jobId: job.id, }); } catch (error) { log("Error queuing document generation:", error.message); return res.status(500).json({ success: false, message: error.message, }); } }; /** * Get job status and result */ exports.getJobStatus = async (req, res) => { try { const { jobId } = req.params; if (!jobId) { return res.status(400).json({ success: false, message: "jobId is required", }); } // Get job from queue const job = await documentQueue.getJob(jobId); if (!job) { return res.status(404).json({ success: false, message: "Job not found", }); } // Get job state const state = await job.getState(); const result = job.returnvalue; const failedReason = job.failedReason; return res.json({ success: true, jobId: job.id, state, // "waiting" | "active" | "completed" | "failed" | "delayed" result: state === "completed" ? result : null, error: state === "failed" ? failedReason : null, attempts: job.attemptsMade, stacktrace: job.stacktrace, }); } catch (error) { log("Error fetching job status:", error.message); return res.status(500).json({ success: false, message: error.message, }); } }; /** * Download generated document */ exports.downloadDocument = async (req, res) => { try { const { uuid } = req.params; const key = `uploads/${uuid}.pdf`; const command = new GetObjectCommand({ Bucket: process.env.AWS_S3_BUCKET_NAME, Key: key, }); const response = await s3.send(command); res.setHeader( "Content-Type", response.ContentType || "application/octet-stream", ); res.setHeader("Content-Disposition", `attachment; filename="${uuid}.pdf"`); response.Body.pipe(res); res.on("finish", async () => { try { await s3.send( new DeleteObjectCommand({ Bucket: process.env.AWS_S3_BUCKET_NAME, Key: key, }), ); log(`Deleted from S3: ${key}`); } catch (err) { log(`Failed to delete ${key}:`, err.message); } }); } catch (error) { log("Download error:", error.message); return res.status(404).json({ success: false, message: "Document not found", }); } }; /** * Cancel/delete a job */ exports.cancelJob = async (req, res) => { try { const { jobId } = req.params; if (!jobId) { return res.status(400).json({ success: false, message: "jobId is required", }); } const job = await documentQueue.getJob(jobId); if (!job) { return res.status(404).json({ success: false, message: "Job not found", }); } await job.remove(); return res.json({ success: true, message: "Job cancelled successfully", jobId, }); } catch (error) { log("Error cancelling job:", error.message); return res.status(500).json({ success: false, message: error.message, }); } };