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 overrideUserType = (req.query.userType || req.query.user_type || '').toString().toLowerCase();
|
||||||
const templateType = (req.query.type || 'contract').toString();
|
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)) {
|
if (!req.user || !['admin', 'super_admin'].includes(req.user.role)) {
|
||||||
return res.status(403).json({ error: 'Forbidden: Admins only' });
|
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);
|
logger.error('[previewLatestForUser] failed to load users row', e && e.message);
|
||||||
}
|
}
|
||||||
if (!userRow) {
|
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
|
// NEW: Preview the actual signed contract the user uploaded/signed (latest by created_at)
|
||||||
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, templateType);
|
try {
|
||||||
if (!latest) {
|
// order by upload_at (actual column) then id as tiebreaker
|
||||||
return res.status(404).json({ error: 'No active template found for user type' });
|
const [docRows] = await db.execute(
|
||||||
}
|
`SELECT object_storage_id
|
||||||
|
FROM user_documents
|
||||||
// Fetch template HTML from S3
|
WHERE user_id = ?
|
||||||
const s3 = new S3Client({
|
AND document_type IN ('contract','signed_contract','contract_pdf','signed_contract_pdf')
|
||||||
region: process.env.EXOSCALE_REGION,
|
AND object_storage_id IS NOT NULL
|
||||||
endpoint: process.env.EXOSCALE_ENDPOINT,
|
ORDER BY upload_at DESC, id DESC
|
||||||
credentials: {
|
LIMIT 1`,
|
||||||
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
[targetUserId]
|
||||||
secretAccessKey: process.env.EXOSCALE_SECRET_KEY
|
);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
});
|
logger.warn('[previewLatestForUser] no contract row with object_storage_id', { targetUserId, requestId: req.id });
|
||||||
const command = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: latest.storageKey });
|
} catch (e) {
|
||||||
const fileObj = await s3.send(command);
|
logger.warn('[previewLatestForUser] reading user_documents failed', e && e.message);
|
||||||
if (!fileObj.Body) {
|
return res.status(500).json({ message: 'Failed to load user contract file' });
|
||||||
return res.status(404).json({ error: 'Template file not available' });
|
|
||||||
}
|
}
|
||||||
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
|
// Build variable map from DB for target user
|
||||||
const vars = {};
|
const vars = {};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user