first commit
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user