diff --git a/controller/documentTemplate/DocumentTemplateController.js b/controller/documentTemplate/DocumentTemplateController.js index 80c120e..b15cece 100644 --- a/controller/documentTemplate/DocumentTemplateController.js +++ b/controller/documentTemplate/DocumentTemplateController.js @@ -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(` + Contract Preview + + + + `); + 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 = {};