feat: add folder structure warning for contract preview in previewLatestForUser function

This commit is contained in:
seaznCode 2026-01-19 23:16:08 +01:00
parent f6067c48fc
commit 5638b98cb8

View File

@ -25,6 +25,7 @@ function saveDebugFile(filename, data, encoding = 'utf8') {
const debugDir = ensureDebugDir(); const debugDir = ensureDebugDir();
const p = path.join(debugDir, filename); const p = path.join(debugDir, filename);
try { try {
let folderStructureWarning = null;
if (Buffer.isBuffer(data)) fs.writeFileSync(p, data); if (Buffer.isBuffer(data)) fs.writeFileSync(p, data);
else fs.writeFileSync(p, data, { encoding }); else fs.writeFileSync(p, data, { encoding });
logger.debug(`[DEBUG] Wrote debug file: ${p}`); logger.debug(`[DEBUG] Wrote debug file: ${p}`);
@ -1458,6 +1459,7 @@ exports.previewLatestForUser = async (req, res) => {
const hasContractFolder = keys.some(k => k.startsWith(`${basePrefix}contract/`)); const hasContractFolder = keys.some(k => k.startsWith(`${basePrefix}contract/`));
const hasGdprFolder = keys.some(k => k.startsWith(`${basePrefix}gdpr/`)); const hasGdprFolder = keys.some(k => k.startsWith(`${basePrefix}gdpr/`));
if (!hasContractFolder && !hasGdprFolder) { if (!hasContractFolder && !hasGdprFolder) {
folderStructureWarning = 'Admin user has to clean up and move the files in exoscale folder';
logger.error('[previewLatestForUser] contract folder structure invalid: Admin user has to clean up and move the files in exoscale folder', { logger.error('[previewLatestForUser] contract folder structure invalid: Admin user has to clean up and move the files in exoscale folder', {
userId: targetUserId, userId: targetUserId,
contractCategory, contractCategory,
@ -1470,6 +1472,11 @@ exports.previewLatestForUser = async (req, res) => {
logger.warn('[previewLatestForUser] contract folder structure check failed', e && (e.stack || e.message)); logger.warn('[previewLatestForUser] contract folder structure check failed', e && (e.stack || e.message));
} }
const jsonWithWarning = (payload) => {
if (!folderStructureWarning) return payload;
return { ...payload, warning: folderStructureWarning };
};
// Choose document_type set based on contractType (aligned with ContractUploadService paths) // Choose document_type set based on contractType (aligned with ContractUploadService paths)
// uploadContract stores under contracts/<category>/<userId>/<contract_type> with document_type 'contract' // uploadContract stores under contracts/<category>/<userId>/<contract_type> with document_type 'contract'
// so use contract_type column to disambiguate between contract vs gdpr // so use contract_type column to disambiguate between contract vs gdpr
@ -1502,7 +1509,8 @@ exports.previewLatestForUser = async (req, res) => {
} }
if (!doc || !doc.object_storage_id) { if (!doc || !doc.object_storage_id) {
return res.status(404).json({ message: `No uploaded ${contractType.toUpperCase()} file found for this user` }); if (folderStructureWarning) res.setHeader('X-Contract-Preview-Warning', folderStructureWarning);
return res.status(404).json(jsonWithWarning({ message: `No uploaded ${contractType.toUpperCase()} file found for this user` }));
} }
try { try {
@ -1526,7 +1534,8 @@ exports.previewLatestForUser = async (req, res) => {
const pdfBuffer = await s3BodyToBuffer(fileObj.Body); const pdfBuffer = await s3BodyToBuffer(fileObj.Body);
if (!pdfBuffer || !pdfBuffer.length) { if (!pdfBuffer || !pdfBuffer.length) {
logger.warn('[previewLatestForUser] S3 returned empty Body', { key: doc.object_storage_id, userId: targetUserId, contractType }); logger.warn('[previewLatestForUser] S3 returned empty Body', { key: doc.object_storage_id, userId: targetUserId, contractType });
return res.status(404).json({ message: `${contractType.toUpperCase()} file not available` }); if (folderStructureWarning) res.setHeader('X-Contract-Preview-Warning', folderStructureWarning);
return res.status(404).json(jsonWithWarning({ message: `${contractType.toUpperCase()} file not available` }));
} }
const b64 = pdfBuffer.toString('base64'); const b64 = pdfBuffer.toString('base64');
@ -1537,14 +1546,17 @@ exports.previewLatestForUser = async (req, res) => {
</body></html> </body></html>
`); `);
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8');
if (folderStructureWarning) res.setHeader('X-Contract-Preview-Warning', folderStructureWarning);
return res.send(html); return res.send(html);
} catch (e) { } catch (e) {
if (e && (e.name === 'NoSuchKey' || (e.$metadata && e.$metadata.httpStatusCode === 404))) { if (e && (e.name === 'NoSuchKey' || (e.$metadata && e.$metadata.httpStatusCode === 404))) {
logger.warn('[previewLatestForUser] object missing in storage', { key: doc.object_storage_id, userId: targetUserId, contractType }); logger.warn('[previewLatestForUser] object missing in storage', { key: doc.object_storage_id, userId: targetUserId, contractType });
return res.status(404).json({ message: `${contractType.toUpperCase()} file not available` }); if (folderStructureWarning) res.setHeader('X-Contract-Preview-Warning', folderStructureWarning);
return res.status(404).json(jsonWithWarning({ message: `${contractType.toUpperCase()} file not available` }));
} }
logger.error('[previewLatestForUser] S3 fetch failed', e && (e.stack || e.message)); logger.error('[previewLatestForUser] S3 fetch failed', e && (e.stack || e.message));
return res.status(500).json({ message: 'Failed to load user document' }); if (folderStructureWarning) res.setHeader('X-Contract-Preview-Warning', folderStructureWarning);
return res.status(500).json(jsonWithWarning({ message: 'Failed to load user document' }));
} }
} catch (err) { } catch (err) {
logger.error('[previewLatestForUser] error', err && err.stack ? err.stack : err); logger.error('[previewLatestForUser] error', err && err.stack ? err.stack : err);