From f6067c48fc35b3a2393a939106c75cb98ea5eb02 Mon Sep 17 00:00:00 2001 From: seaznCode Date: Mon, 19 Jan 2026 22:53:49 +0100 Subject: [PATCH] feat: add user type handling and Exoscale contract folder structure validation in previewLatestForUser function --- .../DocumentTemplateController.js | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/controller/documentTemplate/DocumentTemplateController.js b/controller/documentTemplate/DocumentTemplateController.js index 928398b..227c7ec 100644 --- a/controller/documentTemplate/DocumentTemplateController.js +++ b/controller/documentTemplate/DocumentTemplateController.js @@ -1,6 +1,6 @@ const DocumentTemplateService = require('../../services/template/DocumentTemplateService'); const ContractUploadService = require('../../services/contracts/ContractUploadService'); -const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); +const { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command } = require('@aws-sdk/client-s3'); const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); const PDFDocument = require('pdfkit'); const stream = require('stream'); @@ -1428,6 +1428,48 @@ exports.previewLatestForUser = async (req, res) => { logger.error('[previewLatestForUser] failed to load users row', e && e.message); } + const userTypeParam = (req.query.userType || req.query.user_type || '').toString().toLowerCase(); + const userType = ['personal', 'company'].includes(userTypeParam) + ? userTypeParam + : ((userRow && userRow.user_type) ? String(userRow.user_type).toLowerCase() : 'personal'); + const contractCategory = userType === 'company' ? 'company' : 'personal'; + + // Check if there are files in the user's Exoscale contracts folder but no contract/gdpr subfolders. + try { + const s3ForList = sharedExoscaleClient || 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 + } + }); + const basePrefix = `contracts/${contractCategory}/${targetUserId}/`; + const listResp = await s3ForList.send(new ListObjectsV2Command({ + Bucket: process.env.EXOSCALE_BUCKET, + Prefix: basePrefix, + MaxKeys: 200 + })); + const keys = (listResp && listResp.Contents ? listResp.Contents : []) + .map(item => item && item.Key) + .filter(Boolean); + if (keys.length > 0) { + const hasContractFolder = keys.some(k => k.startsWith(`${basePrefix}contract/`)); + const hasGdprFolder = keys.some(k => k.startsWith(`${basePrefix}gdpr/`)); + if (!hasContractFolder && !hasGdprFolder) { + logger.error('[previewLatestForUser] contract folder structure invalid: Admin user has to clean up and move the files in exoscale folder', { + userId: targetUserId, + contractCategory, + prefix: basePrefix, + sampleKeys: keys.slice(0, 10) + }); + } + } + } catch (e) { + logger.warn('[previewLatestForUser] contract folder structure check failed', e && (e.stack || e.message)); + } + // Choose document_type set based on contractType (aligned with ContractUploadService paths) // uploadContract stores under contracts/// with document_type 'contract' // so use contract_type column to disambiguate between contract vs gdpr