feat: enhance contract preview functionality with user-specific document retrieval
This commit is contained in:
parent
92a54866b4
commit
b2a3132bea
@ -1285,6 +1285,13 @@ exports.previewLatestForUser = async (req, res) => {
|
||||
const overrideUserType = (req.query.userType || req.query.user_type || '').toString().toLowerCase();
|
||||
const templateType = (req.query.type || 'contract').toString();
|
||||
|
||||
logger.info('[previewLatestForUser] start', {
|
||||
targetUserId,
|
||||
overrideUserType,
|
||||
templateType,
|
||||
requestId: req.id
|
||||
});
|
||||
|
||||
if (!req.user || !['admin', 'super_admin'].includes(req.user.role)) {
|
||||
return res.status(403).json({ error: 'Forbidden: Admins only' });
|
||||
}
|
||||
@ -1302,32 +1309,91 @@ exports.previewLatestForUser = async (req, res) => {
|
||||
logger.error('[previewLatestForUser] failed to load users row', e && e.message);
|
||||
}
|
||||
if (!userRow) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
// Fallback: allow preview based solely on existing contract file even if user row is missing
|
||||
logger.warn('[previewLatestForUser] user not found, continuing with contract lookup only', { targetUserId, requestId: req.id });
|
||||
}
|
||||
|
||||
const userType = (overrideUserType === 'personal' || overrideUserType === 'company') ? overrideUserType : userRow.user_type;
|
||||
const userType = (overrideUserType === 'personal' || overrideUserType === 'company')
|
||||
? overrideUserType
|
||||
: (userRow ? userRow.user_type : 'personal');
|
||||
logger.info('[previewLatestForUser] user resolved/fallback', { targetUserId, userType, requestId: req.id });
|
||||
|
||||
// Find the latest active template for this user type
|
||||
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, templateType);
|
||||
if (!latest) {
|
||||
return res.status(404).json({ error: 'No active template found for user type' });
|
||||
}
|
||||
|
||||
// Fetch template HTML from S3
|
||||
const s3 = new S3Client({
|
||||
region: process.env.EXOSCALE_REGION,
|
||||
endpoint: process.env.EXOSCALE_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
||||
secretAccessKey: process.env.EXOSCALE_SECRET_KEY
|
||||
// NEW: Preview the actual signed contract the user uploaded/signed (latest by created_at)
|
||||
try {
|
||||
// order by upload_at (actual column) then id as tiebreaker
|
||||
const [docRows] = await db.execute(
|
||||
`SELECT object_storage_id
|
||||
FROM user_documents
|
||||
WHERE user_id = ?
|
||||
AND document_type IN ('contract','signed_contract','contract_pdf','signed_contract_pdf')
|
||||
AND object_storage_id IS NOT NULL
|
||||
ORDER BY upload_at DESC, id DESC
|
||||
LIMIT 1`,
|
||||
[targetUserId]
|
||||
);
|
||||
const docRowsArr = Array.isArray(docRows) ? docRows : (docRows ? [docRows] : []);
|
||||
logger.info('[previewLatestForUser] contract rows fetched', {
|
||||
targetUserId,
|
||||
count: docRowsArr.length,
|
||||
rows: docRowsArr,
|
||||
requestId: req.id
|
||||
});
|
||||
const doc = docRowsArr[0];
|
||||
if (doc && doc.object_storage_id) {
|
||||
// Use explicit endpoint + path-style to match Exoscale object storage
|
||||
const s3File = new S3Client({
|
||||
region: process.env.EXOSCALE_REGION,
|
||||
endpoint: process.env.EXOSCALE_ENDPOINT,
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
||||
secretAccessKey: process.env.EXOSCALE_SECRET_KEY
|
||||
}
|
||||
});
|
||||
logger.info('[previewLatestForUser] attempting S3 fetch', {
|
||||
bucket: process.env.EXOSCALE_BUCKET,
|
||||
key: doc.object_storage_id,
|
||||
userId: targetUserId
|
||||
});
|
||||
const cmd = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: doc.object_storage_id });
|
||||
const fileObj = await s3File.send(cmd);
|
||||
if (!fileObj.Body) {
|
||||
logger.warn('[previewLatestForUser] S3 returned empty Body', {
|
||||
bucket: process.env.EXOSCALE_BUCKET,
|
||||
key: doc.object_storage_id,
|
||||
userId: targetUserId
|
||||
});
|
||||
return res.status(404).json({ message: 'Contract file not available' });
|
||||
}
|
||||
const chunks = [];
|
||||
for await (const chunk of fileObj.Body) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
const pdfBuffer = Buffer.concat(chunks);
|
||||
logger.info('[previewLatestForUser] S3 fetch success', {
|
||||
bucket: process.env.EXOSCALE_BUCKET,
|
||||
key: doc.object_storage_id,
|
||||
userId: targetUserId,
|
||||
size: pdfBuffer.length
|
||||
});
|
||||
const b64 = pdfBuffer.toString('base64');
|
||||
const html = ensureHtmlDocument(`
|
||||
<html><head><meta charset="utf-8" /><title>Contract Preview</title></head>
|
||||
<body style="margin:0;padding:0;height:100vh;background:#0f172a;">
|
||||
<embed src="data:application/pdf;base64,${b64}" type="application/pdf" style="width:100%;height:100%;border:none;" />
|
||||
</body></html>
|
||||
`);
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
return res.send(html);
|
||||
}
|
||||
});
|
||||
const command = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: latest.storageKey });
|
||||
const fileObj = await s3.send(command);
|
||||
if (!fileObj.Body) {
|
||||
return res.status(404).json({ error: 'Template file not available' });
|
||||
logger.warn('[previewLatestForUser] no contract row with object_storage_id', { targetUserId, requestId: req.id });
|
||||
} catch (e) {
|
||||
logger.warn('[previewLatestForUser] reading user_documents failed', e && e.message);
|
||||
return res.status(500).json({ message: 'Failed to load user contract file' });
|
||||
}
|
||||
let html = await streamToString(fileObj.Body, latest.id);
|
||||
|
||||
// If no uploaded/signed contract exists, return not found (do not fallback to template)
|
||||
return res.status(404).json({ message: 'No signed contract found for this user' });
|
||||
|
||||
// Build variable map from DB for target user
|
||||
const vars = {};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user