import { Request, Response } from 'express'; import { v4 as uuidv4 } from 'uuid'; import { Database } from '../database'; import { SignerImprovedService } from '../services/signer-improved'; import { SessionManager } from '../utils/session-manager'; import { authTokens } from '../utils/auth-tokens'; import { ProcessInfo, ProcessData, ProcessRoles, EOfficeStatus } from '../types'; import { config } from '../config'; import { Logger } from '../utils/logger'; import { AppError, ErrorCode, NotFoundError, ExternalServiceError, BusinessRuleError } from '../types/errors'; import { asyncHandler } from '../middleware/error-handler'; export class ProcessImprovedController { static getUserProcess = asyncHandler(async (req: Request, res: Response): Promise => { const requestId = req.headers['x-request-id'] as string; Logger.info('User process request initiated', { requestId }); // Find the full token data which should contain the original idNotUser data const userAuth = authTokens.find(auth => auth.authToken === req.idNotUser!.authToken); if (!userAuth || !userAuth.idNotUser) { throw new NotFoundError('Données utilisateur non trouvées. Veuillez vous reconnecter.', requestId); } const { pairingId } = req.query; // Execute signer operations with retry logic const processResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.getUserProcessByIdnot(userAuth.idNotUser.idNot); }, 'getUserProcessByIdnot', 3 ); if (!processResult.success) { throw new ExternalServiceError('Signer', processResult.error?.message || 'Failed to get user process', requestId); } let process: ProcessInfo | null = processResult.data || null; if (!process) { Logger.info('No existing process found, creating new one', { requestId, userIdNot: userAuth.idNotUser.idNot }); // Get UUID from database let uuid: string; try { const result = await Database.query('SELECT uid FROM users WHERE "idNot" = $1', [userAuth.idNotUser.idNot]); uuid = result.rows.length > 0 ? result.rows[0].uid : null; } catch (error) { Logger.error('Error fetching UUID by idNot', { requestId, error: error instanceof Error ? error.message : 'Unknown error' }); uuid = ''; } if (!uuid) { Logger.info('No existing UUID found in db, generating new one', { requestId }); uuid = uuidv4(); } const processData: ProcessData = { uid: uuid, utype: 'collaborator', idNot: userAuth.idNotUser.idNot, office: { idNot: userAuth.idNotUser.office.idNot, }, role: userAuth.idNotUser.role, office_role: userAuth.idNotUser.office_role, contact: userAuth.idNotUser.contact, }; const privateFields = Object.keys(processData); const allFields = [...privateFields, 'roles']; // Make those fields public privateFields.splice(privateFields.indexOf('uid'), 1); privateFields.splice(privateFields.indexOf('utype'), 1); privateFields.splice(privateFields.indexOf('idNot'), 1); // Get pairing ID with retry const pairingResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.getPairingId(); }, 'getPairingId', 3 ); if (!pairingResult.success) { throw new ExternalServiceError('Signer', pairingResult.error?.message || 'Failed to get pairing ID', requestId); } const validatorId = pairingResult.data!.pairingId; const roles: ProcessRoles = { owner: { members: [pairingId as string, validatorId], validation_rules: [ { quorum: 0.1, fields: allFields, min_sig_member: 1, } ], storages: [config.defaultStorage] }, apophis: { members: [pairingId as string, validatorId], validation_rules: [], storages: [] } }; // Create process with retry const createResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.createProcess(processData, privateFields, roles); }, 'createProcess', 3 ); if (!createResult.success) { throw new ExternalServiceError('Signer', createResult.error?.message || 'Failed to create process', requestId); } Logger.info('Created new process', { requestId, processId: createResult.data!.processId }); process = { processId: createResult.data!.processId || '', processData: createResult.data!.data }; } else { Logger.info('Using existing process', { requestId, processId: process.processId }); } // Check if process is committed and handle role updates const processManagementResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { const allProcesses = await signerClient.getOwnedProcesses(); if (allProcesses && process) { const processStates = allProcesses.processes[process.processId].states; const isNotCommited = processStates.length === 2 && processStates[1].commited_in === processStates[0].commited_in; if (isNotCommited) { Logger.info('Process not committed, committing it', { requestId, processId: process.processId }); await signerClient.validateState(process.processId, processStates[0].state_id); } // Check pairing ID in roles let roles: ProcessRoles; if (isNotCommited) { const firstState = processStates[0]; roles = firstState.roles; } else { const tip = processStates[processStates.length - 1].commited_in; const lastState = processStates.findLast((state: any) => state.commited_in !== tip); roles = lastState.roles; } if (!roles) { throw new Error('No roles found'); } else if (!roles['owner']) { throw new Error('No owner role found'); } if (!roles['owner'].members.includes(req.query.pairingId as string)) { Logger.info('Adding new pairingId to owner role', { requestId, pairingId: req.query.pairingId, processId: process.processId }); roles['owner'].members.push(req.query.pairingId as string); const updatedProcessReturn = await signerClient.updateProcess(process.processId, {}, [], roles); const processId = updatedProcessReturn.updatedProcess.process_id; const stateId = updatedProcessReturn.updatedProcess.diffs[0].state_id; await signerClient.notifyUpdate(processId, stateId); await signerClient.validateState(processId, stateId); } } return process; }, 'processManagement', 2 ); if (!processManagementResult.success) { throw new ExternalServiceError('Signer', processManagementResult.error?.message || 'Failed to manage process', requestId); } Logger.info('User process request completed successfully', { requestId, processId: process?.processId }); res.json({ success: true, data: processManagementResult.data }); }); static getOfficeProcess = asyncHandler(async (req: Request, res: Response): Promise => { const requestId = req.headers['x-request-id'] as string; Logger.info('Office process request initiated', { requestId }); const userAuth = authTokens.find(auth => auth.authToken === req.idNotUser!.authToken); if (!userAuth || !userAuth.idNotUser) { throw new NotFoundError('Données utilisateur non trouvées. Veuillez vous reconnecter.', requestId); } // Check office status if (userAuth.idNotUser.office.office_status !== EOfficeStatus.ACTIVATED) { throw new BusinessRuleError('Office not activated', undefined, requestId); } // Get office process with retry const processResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.getOfficeProcessByIdnot(userAuth.idNotUser.office.idNot); }, 'getOfficeProcessByIdnot', 3 ); if (!processResult.success) { throw new ExternalServiceError('Signer', processResult.error?.message || 'Failed to get office process', requestId); } let process: ProcessInfo | null = processResult.data || null; if (!process) { Logger.info('No existing office process found, creating new one', { requestId, officeIdNot: userAuth.idNotUser.office.idNot }); // Get validator ID with retry const pairingResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.getPairingId(); }, 'getPairingId', 3 ); if (!pairingResult.success) { throw new ExternalServiceError('Signer', pairingResult.error?.message || 'Failed to get validator ID', requestId); } const validatorId = pairingResult.data!.pairingId; if (!validatorId) { throw new BusinessRuleError('No validator id found', undefined, requestId); } // Get UUID from database let uuid: string; try { const result = await Database.query('SELECT uid FROM offices WHERE "idNot" = $1', [userAuth.idNotUser.office.idNot]); uuid = result.rows.length > 0 ? result.rows[0].uid : null; } catch (error) { Logger.error('Error fetching office UUID by idNot', { requestId, error: error instanceof Error ? error.message : 'Unknown error' }); uuid = ''; } if (!uuid) { Logger.info('No existing office UUID found in db, generating new one', { requestId }); uuid = uuidv4(); } const processData: ProcessData = { uid: uuid, utype: 'office', ...userAuth.idNotUser.office, }; const privateFields = Object.keys(processData); const roles: ProcessRoles = { owner: { members: [validatorId], validation_rules: [], storages: [] }, apophis: { members: [validatorId], validation_rules: [], storages: [] } }; // Create office process with retry const createResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.createProcess(processData, privateFields, roles); }, 'createOfficeProcess', 3 ); if (!createResult.success) { throw new ExternalServiceError('Signer', createResult.error?.message || 'Failed to create office process', requestId); } Logger.info('Created new office process', { requestId, processId: createResult.data!.processId }); process = { processId: createResult.data!.processId || '', processData: createResult.data!.data }; } Logger.info('Office process request completed successfully', { requestId, processId: process?.processId }); res.json({ success: true, data: process }); }); static authenticateClient = asyncHandler(async (req: Request, res: Response): Promise => { const requestId = req.headers['x-request-id'] as string; const { pairingId } = req.body; if (!pairingId) { throw new BusinessRuleError('Missing pairingId', undefined, requestId); } Logger.info('Client authentication initiated', { requestId, pairingId }); // This should be implemented properly based on your business logic // For now, just clean up the session SessionManager.deleteSession(req.session!.id); Logger.info('Client authentication completed', { requestId }); res.json({ success: true, message: 'Client authentication successful', data: {} }); }); static getPhoneNumberForEmail = asyncHandler(async (req: Request, res: Response): Promise => { const requestId = req.headers['x-request-id'] as string; const { email } = req.body; if (!email) { throw new BusinessRuleError('Missing email', undefined, requestId); } Logger.info('Phone number lookup initiated', { requestId, email }); const phoneResult = await SignerImprovedService.executeWithRetry( async (signerClient) => { return await signerClient.getPhoneNumberForEmail(email); }, 'getPhoneNumberForEmail', 3 ); if (!phoneResult.success) { throw new ExternalServiceError('Signer', phoneResult.error?.message || 'Failed to get phone number', requestId); } const phoneNumber = phoneResult.data; if (!phoneNumber) { throw new NotFoundError('No phone number found for this email', requestId); } Logger.info('Phone number lookup completed', { requestId, email }); res.json({ success: true, message: 'Phone number retrieved successfully', phoneNumber: phoneNumber }); }); // Health check endpoint for signer service static getSignerHealth = asyncHandler(async (req: Request, res: Response): Promise => { const healthStatus = SignerImprovedService.getHealthStatus(); res.json({ success: true, data: { signer: healthStatus, timestamp: new Date().toISOString() } }); }); // Force reconnection endpoint (for debugging/admin use) static forceSignerReconnect = asyncHandler(async (req: Request, res: Response): Promise => { const requestId = req.headers['x-request-id'] as string; Logger.info('Force signer reconnection requested', { requestId }); const reconnectResult = await SignerImprovedService.forceReconnect(); if (!reconnectResult.success) { throw new ExternalServiceError('Signer', reconnectResult.error?.message || 'Failed to reconnect', requestId); } Logger.info('Force signer reconnection completed', { requestId }); res.json({ success: true, message: 'Signer reconnection initiated', data: SignerImprovedService.getHealthStatus() }); }); }