lecoffre-back-mini/src/controllers/process-improved.controller.ts
2025-09-07 21:10:39 +02:00

450 lines
14 KiB
TypeScript

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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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()
});
});
}