const UnitOfWork = require('../../database/UnitOfWork'); const ContractUploadService = require('../../services/contracts/ContractUploadService'); const UserDocumentRepository = require('../../repositories/documents/UserDocumentRepository'); const { logger } = require('../../middleware/logger'); function normalizeSignature(signatureImage) { if (!signatureImage) return null; const str = String(signatureImage); const m = str.match(/^data:(.+);base64,(.+)$/); if (m && m[2]) { const mime = m[1] || 'image/png'; const base64 = m[2]; const size = Buffer.from(base64, 'base64').length; return { base64, mime, size }; } try { const size = Buffer.from(str, 'base64').length; return { base64: str, mime: 'image/png', size }; } catch (e) { logger.warn('[ContractUploadController] normalizeSignature failed to parse provided signature'); return null; } } class ContractUploadController { static async uploadPersonalContract(req, res) { const userId = req.user.userId; logger.info(`[ContractUploadController] uploadPersonalContract called for userId: ${userId}`); const file = req.file; // optional, we now generate from templates when absent const contractData = req.body.contractData ? JSON.parse(req.body.contractData) : undefined; const signatureImage = req.body.signatureImage; const unitOfWork = new UnitOfWork(); await unitOfWork.start(); try { const signatureMeta = normalizeSignature(signatureImage); const repo = new UserDocumentRepository(unitOfWork); if (signatureMeta) { await repo.insertDocument({ userId, documentType: 'signature', contractType: 'contract', objectStorageId: null, signatureBase64: signatureMeta.base64, originalFilename: 'signature.png', fileSize: signatureMeta.size, mimeType: signatureMeta.mime }); logger.info('[ContractUploadController] signature stored for user', { userId, bytes: signatureMeta.size }); } const uploads = []; const skipped = []; const tryUpload = async ({ contract_type, fileOverride }) => { try { const result = await ContractUploadService.uploadContract({ userId, file: fileOverride, documentType: 'contract', contractCategory: 'personal', unitOfWork, contractData, signatureImage: signatureMeta ? signatureMeta.base64 : null, contract_type, user_type: 'personal' }); uploads.push({ contract_type, ...result }); return true; } catch (e) { const msg = (e && e.message) ? String(e.message) : ''; if (msg.includes('No active') && msg.includes('template found')) { skipped.push({ contract_type, reason: 'no_active_template' }); logger.info('[ContractUploadController] template missing, skipping', { userId, contract_type }); return false; } throw e; } }; const didUploadContract = await tryUpload({ contract_type: 'contract', fileOverride: file }); const didUploadGdpr = await tryUpload({ contract_type: 'gdpr', fileOverride: undefined }); if (!didUploadContract && !didUploadGdpr) { throw new Error('No active documents are available at this moment.'); } // Cleanup standalone signature record after contracts are saved if (signatureMeta) { await repo.deleteSignatureDocumentsForUser(userId); logger.info('[ContractUploadController] signature cleanup completed for user', { userId }); } await unitOfWork.commit(); logger.info(`[ContractUploadController] uploadPersonalContract success for userId: ${userId}`); res.json({ success: true, uploads, skipped, downloadUrls: uploads.map(u => u.url || null) }); } catch (error) { logger.error(`[ContractUploadController] uploadPersonalContract error for userId: ${userId}`, { error }); await unitOfWork.rollback(error); res.status(400).json({ success: false, message: error.message }); } } static async uploadCompanyContract(req, res) { const userId = req.user.userId; logger.info(`[ContractUploadController] uploadCompanyContract called for userId: ${userId}`); const file = req.file; const contractData = req.body.contractData ? JSON.parse(req.body.contractData) : undefined; const signatureImage = req.body.signatureImage; const unitOfWork = new UnitOfWork(); await unitOfWork.start(); try { const signatureMeta = normalizeSignature(signatureImage); const repo = new UserDocumentRepository(unitOfWork); if (signatureMeta) { await repo.insertDocument({ userId, documentType: 'signature', contractType: 'contract', objectStorageId: null, signatureBase64: signatureMeta.base64, originalFilename: 'signature.png', fileSize: signatureMeta.size, mimeType: signatureMeta.mime }); logger.info('[ContractUploadController] signature stored for company user', { userId, bytes: signatureMeta.size }); } const uploads = []; const skipped = []; const tryUpload = async ({ contract_type, fileOverride }) => { try { const result = await ContractUploadService.uploadContract({ userId, file: fileOverride, documentType: 'contract', contractCategory: 'company', unitOfWork, contractData, signatureImage: signatureMeta ? signatureMeta.base64 : null, contract_type, user_type: 'company' }); uploads.push({ contract_type, ...result }); return true; } catch (e) { const msg = (e && e.message) ? String(e.message) : ''; if (msg.includes('No active') && msg.includes('template found')) { skipped.push({ contract_type, reason: 'no_active_template' }); logger.info('[ContractUploadController] template missing, skipping', { userId, contract_type }); return false; } throw e; } }; const didUploadContract = await tryUpload({ contract_type: 'contract', fileOverride: file }); const didUploadGdpr = await tryUpload({ contract_type: 'gdpr', fileOverride: undefined }); if (!didUploadContract && !didUploadGdpr) { throw new Error('No active documents are available at this moment.'); } await unitOfWork.commit(); if (signatureMeta) { await repo.deleteSignatureDocumentsForUser(userId); logger.info('[ContractUploadController] signature cleanup completed for company user', { userId }); } logger.info(`[ContractUploadController] uploadCompanyContract success for userId: ${userId}`); res.json({ success: true, uploads, skipped, downloadUrls: uploads.map(u => u.url || null) }); } catch (error) { logger.error(`[ContractUploadController] uploadCompanyContract error for userId: ${userId}`, { error }); await unitOfWork.rollback(error); res.status(400).json({ success: false, message: error.message }); } } } module.exports = ContractUploadController;