Conversion to typescript

This commit is contained in:
Sosthene 2025-09-07 15:25:48 +02:00
parent 1465c9dfd2
commit 8462b99586
9 changed files with 1324 additions and 261 deletions

39
global.d.ts vendored Normal file
View File

@ -0,0 +1,39 @@
// Global type declarations for packages without @types
declare module 'ovh' {
interface OVHConfig {
appKey: string;
appSecret: string;
consumerKey: string;
}
interface OVHClient {
request(method: string, path: string, params: any, callback: (error: any, result: any) => void): void;
}
function ovh(config: OVHConfig): OVHClient;
export = ovh;
}
declare module '@mailchimp/mailchimp_transactional' {
interface MailchimpMessage {
template_name: string;
template_content: any[];
message: {
global_merge_vars: Array<{ name: string; content: string }>;
from_email: string;
from_name: string;
subject: string;
to: Array<{ email: string; type: string }>;
};
}
interface MailchimpClient {
messages: {
sendTemplate(message: MailchimpMessage): Promise<any>;
};
}
function mailchimp(apiKey: string): MailchimpClient;
export = mailchimp;
}

View File

@ -2,10 +2,16 @@
"name": "lecoffre-back-mini",
"version": "1.0.0",
"description": "Mini serveur avec une route /api/ping",
"main": "src/server.js",
"main": "dist/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
"build": "tsc",
"start": "node dist/server.js",
"dev": "ts-node src/server.ts",
"watch": "nodemon --exec ts-node src/server.ts",
"dev:js": "nodemon src/server.js",
"test:db": "npm run build && node test-db-init.js",
"test:rattachements": "node test-rattachements-endpoint.js",
"test:quick": "node quick-test-rattachements.js"
},
"dependencies": {
"@mailchimp/mailchimp_transactional": "^1.0.59",
@ -20,6 +26,14 @@
"uuid": "^11.1.0"
},
"devDependencies": {
"nodemon": "^3.0.1"
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.11.19",
"@types/node-fetch": "^2.6.11",
"@types/pg": "^8.11.0",
"@types/uuid": "^9.0.8",
"nodemon": "^3.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}

159
quick-test-rattachements.js Executable file
View File

@ -0,0 +1,159 @@
#!/usr/bin/env node
const fetch = require('node-fetch');
const { SDKSignerClient } = require('sdk-signer-client');
// Quick test configuration
const BASE_URL = 'http://localhost:8080';
const ENDPOINT = '/api/v1/idnot/user/rattachements'; // Base endpoint, idnot will be added as path parameter
const signerConfig = {
url: process.env.SIGNER_WS_URL || 'ws://localhost:9090',
apiKey: process.env.SIGNER_API_KEY || 'your-api-key-change-this',
timeout: 30000,
reconnectInterval: 5000,
maxReconnectAttempts: 3
};
// Test with a specific IDNot
async function testWithIdNot(idNot) {
if (!idNot) {
console.log('💡 Usage: node quick-test-rattachements.js [idNot]');
console.log(' Example: node quick-test-rattachements.js 12345');
console.log(' Example: node quick-test-rattachements.js (no parameter to test without idNot)');
console.log(' URL format: /api/v1/idnot/user/{idnot}/rattachements');
return;
}
console.log(`🆔 Testing with IDNot: ${idNot}`);
// Build URL with path parameter
let url = `${BASE_URL}${ENDPOINT}`;
url += `?idNot=${encodeURIComponent(idNot)}`;
console.log(`📍 URL: ${url}`);
console.log('=' .repeat(60));
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
console.log(`📊 Status: ${response.status} ${response.statusText}`);
const responseText = await response.text();
console.log(`📄 Response length: ${responseText.length} characters`);
let data;
try {
data = JSON.parse(responseText);
console.log('📋 Parsed JSON response:');
console.log(JSON.stringify(data, null, 2));
} catch (e) {
console.log('📋 Raw response (not JSON):');
console.log(responseText);
return;
}
for (const office of data) {
let officeRattachementsData = [];
// Now test the office rattachements
const officeRattachements = await fetch(`${BASE_URL}/api/v1/idnot/office/rattachements?idNot=${office.ou}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
console.log(`📊 Status: ${officeRattachements.status} ${officeRattachements.statusText}`);
const officeRattachementsText = await officeRattachements.text();
console.log(`📄 Response length: ${officeRattachementsText.length} characters`);
try {
officeRattachementsData = JSON.parse(officeRattachementsText);
console.log('📋 Parsed JSON response:');
console.log(JSON.stringify(officeRattachementsData, null, 2));
} catch (e) {
console.log('📋 Raw response (not JSON):');
console.log(officeRattachementsText);
return;
}
// Now try to create a new process with all the users that have `activite` set to `En exercice`
const usersToAdd = officeRattachementsData.result.filter(user => user.activite === 'En exercice');
console.log(`📋 Users to add: ${usersToAdd.length}`);
console.log(JSON.stringify(usersToAdd, null, 2));
// Probably the idnot number should be public so that caller can easily find the processId?
// Caller can now create the office process with the following data
const processData = {
name: 'New Process',
description: 'New Process Description',
timestamp: new Date().toISOString(),
office: office.ou,
};
const privateFields = Object.keys(processData);
privateFields.splice(privateFields.indexOf('office'), 1); // Make office public data
const roles = {
owner: {
members: usersToAdd.map(user => user.uid),
validation_rules: [
{
quorum: 0.1,
fields: [...privateFields, 'roles', 'office'],
min_sig_member: 1,
},
],
storages: ["https://dev3.4nkweb.com/storage"]
},
apophis: {
members: usersToAdd.map(user => user.uid),
validation_rules: [],
storages: []
}
};
}
} catch (error) {
console.log(`💥 Error: ${error.message}`);
}
}
// Main execution
async function main() {
const idNot = process.argv[2]; // Get IDNot from command line argument
console.log('🚀 Quick Rattachements Endpoint Test');
console.log('=' .repeat(60));
// Check if server is running
try {
const healthCheck = await fetch(`${BASE_URL}/api/v1/health`);
if (healthCheck.ok) {
console.log('✅ Server is running');
} else {
console.log(`⚠️ Server responded but health check failed with status: ${healthCheck.status}`);
}
} catch (error) {
console.log('❌ Server is not responding');
console.log('💡 Make sure to start your server first with: npm run dev');
return;
}
console.log('');
await testWithIdNot(idNot);
console.log('\n💡 Usage: node quick-test-rattachements.js [idNot]');
console.log(' Example: node quick-test-rattachements.js 12345');
console.log(' Example: node quick-test-rattachements.js (no parameter to test without idNot)');
console.log(' URL format: /api/v1/idnot/user/{idnot}/rattachements');
}
main().catch(console.error);

View File

@ -1,22 +1,24 @@
const { Pool } = require('pg');
require('dotenv').config();
import { Pool, QueryResult, PoolConfig } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config();
/**
* Configuration de la base de données PostgreSQL
*/
const dbConfig = {
const dbConfig: PoolConfig = {
user: process.env.DB_USER || 'postgres',
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'prd',
password: process.env.DB_PASSWORD || 'admin',
port: process.env.DB_PORT || 5432,
port: parseInt(process.env.DB_PORT || '5432'),
// Configuration du pool de connexions
max: parseInt(process.env.DB_POOL_MAX) || 20, // Nombre maximum de connexions dans le pool
min: parseInt(process.env.DB_POOL_MIN) || 2, // Nombre minimum de connexions maintenues
idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT) || 30000, // Temps d'inactivité avant fermeture
connectionTimeoutMillis: parseInt(process.env.DB_CONNECTION_TIMEOUT) || 2000, // Timeout pour établir une connexion
acquireTimeoutMillis: parseInt(process.env.DB_ACQUIRE_TIMEOUT) || 60000, // Timeout pour acquérir une connexion
max: parseInt(process.env.DB_POOL_MAX || '20'), // Nombre maximum de connexions dans le pool
min: parseInt(process.env.DB_POOL_MIN || '2'), // Nombre minimum de connexions maintenues
idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT || '30000'), // Temps d'inactivité avant fermeture
connectionTimeoutMillis: parseInt(process.env.DB_CONNECTION_TIMEOUT || '2000'), // Timeout pour établir une connexion
// acquireTimeoutMillis: parseInt(process.env.DB_ACQUIRE_TIMEOUT || '60000'), // Timeout pour acquérir une connexion
// Configuration SSL si nécessaire
ssl: process.env.DB_SSL === 'true' ? {
@ -32,22 +34,22 @@ const pool = new Pool(dbConfig);
/**
* Gestionnaire d'erreur pour le pool
*/
pool.on('error', (err) => {
pool.on('error', (err: Error) => {
console.error('PostgreSQL Error:', err);
});
/**
* Classe pour gérer les opérations de base de données
*/
class Database {
export class Database {
/**
* Exécute une requête SQL avec des paramètres
* @param {string} text - La requête SQL
* @param {Array} params - Les paramètres de la requête
* @returns {Promise<Object>} - Le résultat de la requête
* @param text - La requête SQL
* @param params - Les paramètres de la requête
* @returns Promise<QueryResult> - Le résultat de la requête
*/
static async query(text, params) {
static async query(text: string, params?: any[]): Promise<QueryResult> {
try {
return await pool.query(text, params);
} catch (error) {
@ -58,14 +60,14 @@ class Database {
/**
* Teste la connexion à la base de données
* @returns {Promise<boolean>} - True si la connexion est réussie
* @returns Promise<boolean> - True si la connexion est réussie
*/
static async testConnection() {
static async testConnection(): Promise<boolean> {
try {
const result = await this.query('SELECT NOW() as current_time');
console.log('Database connection successful:', result.rows[0].current_time);
return true;
} catch (error) {
} catch (error: any) {
console.error('Database connection failed:', error.message);
return false;
}
@ -73,9 +75,9 @@ class Database {
/**
* Ferme toutes les connexions du pool
* @returns {Promise<void>}
* @returns Promise<void>
*/
static async close() {
static async close(): Promise<void> {
try {
await pool.end();
console.log('PostgreSQL connection pool closed');
@ -99,7 +101,3 @@ process.on('SIGTERM', async () => {
await Database.close();
process.exit(0);
});
module.exports = {
Database
};

File diff suppressed because it is too large Load Diff

180
src/types.ts Normal file
View File

@ -0,0 +1,180 @@
// Type definitions for the application
export enum ECivility {
MALE = 'MALE',
FEMALE = 'FEMALE',
OTHERS = 'OTHERS'
}
export enum EOfficeStatus {
ACTIVATED = 'ACTIVATED',
DESACTIVATED = 'DESACTIVATED'
}
export enum EIdnotRole {
DIRECTEUR = "Directeur général du CSN",
NOTAIRE_TITULAIRE = "Notaire titulaire",
NOTAIRE_ASSOCIE = "Notaire associé",
NOTAIRE_SALARIE = "Notaire salarié",
COLLABORATEUR = "Collaborateur",
SECRETAIRE_GENERAL = "Secrétaire général",
SUPPLEANT = "Suppléant",
ADMINISTRATEUR = "Administrateur",
RESPONSABLE = "Responsable",
CURATEUR = "Curateur",
}
export enum ETemplates {
DOCUMENT_ASKED = "DOCUMENT_ASKED",
DOCUMENT_REFUSED = "DOCUMENT_REFUSED",
DOCUMENT_RECAP = "DOCUMENT_RECAP",
SUBSCRIPTION_INVITATION = "SUBSCRIPTION_INVITATION",
DOCUMENT_REMINDER = "DOCUMENT_REMINDER",
DOCUMENT_SEND = "DOCUMENT_SEND",
}
export interface Address {
address: string;
city: string;
zip_code: number;
}
export interface Office {
idNot: string;
name: string;
crpcen: string;
office_status: EOfficeStatus;
address: Address;
status: string;
}
export interface Contact {
first_name: string;
last_name: string;
email: string;
phone_number: string;
cell_phone_number: string;
civility: ECivility;
}
export interface Role {
name: string;
}
export interface OfficeRole {
name: string;
}
export interface IdNotUser {
idNot: string;
office: Office;
role: Role;
contact: Contact;
office_role: OfficeRole | null;
}
export interface AuthToken {
idNot: string;
authToken: string;
idNotUser: IdNotUser;
pairingId: string | null;
defaultStorage: string | null;
createdAt: number;
expiresAt: number;
}
export interface Session {
id: string;
phoneNumber: string;
userData: any;
createdAt: number;
expiresAt: number;
}
export interface VerificationCode {
code: number;
timestamp: number;
attempts: number;
}
export interface SmsConfig {
OVH_APP_KEY?: string;
OVH_APP_SECRET?: string;
OVH_CONSUMER_KEY?: string;
OVH_SMS_SERVICE_NAME?: string;
SMS_FACTOR_TOKEN?: string;
PORT: number;
}
export interface EmailConfig {
MAILCHIMP_API_KEY?: string;
MAILCHIMP_KEY?: string;
MAILCHIMP_LIST_ID?: string;
PORT: number;
FROM_EMAIL: string;
FROM_NAME: string;
}
export interface StripeConfig {
STRIPE_SECRET_KEY?: string;
STRIPE_WEBHOOK_SECRET?: string;
APP_HOST: string;
}
// SignerConfig is now imported from sdk-signer-client as ClientConfig
export interface Subscription {
type: 'STANDARD' | 'UNLIMITED';
seats?: number;
}
export interface PendingEmail {
to: string;
templateName: string;
subject: string;
templateVariables: Record<string, string>;
attempts: number;
lastAttempt: number;
}
export interface ProcessInfo {
processId: string;
processData: any;
}
export interface ValidationRule {
quorum: number;
fields: string[];
min_sig_member: number;
}
export interface ProcessRole {
members: string[];
validation_rules: ValidationRule[];
storages: string[];
}
export interface ProcessRoles {
owner: ProcessRole;
apophis: ProcessRole;
[key: string]: ProcessRole;
}
export interface ProcessData {
uid: string;
utype: string;
[key: string]: any;
}
// Express Request extensions
declare global {
namespace Express {
interface Request {
session?: Session;
idNotUser?: {
idNot: string;
authToken: string;
};
}
}
}

27
test-db-init.js Normal file
View File

@ -0,0 +1,27 @@
const { Database } = require('./dist/database');
async function testDatabaseInitialization() {
try {
console.log('Testing database connection...');
const isConnected = await Database.testConnection();
if (!isConnected) {
console.error('❌ Database connection failed');
return;
}
console.log('✅ Database connection successful');
console.log('\nTesting basic query...');
const result = await Database.query('SELECT NOW() as current_time');
console.log(`✅ Basic query successful: ${result.rows[0].current_time}`);
} catch (error) {
console.error('❌ Test failed:', error.message);
} finally {
await Database.close();
console.log('\nDatabase connection closed');
}
}
testDatabaseInitialization();

223
test-rattachements-endpoint.js Executable file
View File

@ -0,0 +1,223 @@
#!/usr/bin/env node
const fetch = require('node-fetch');
// Test configuration
const BASE_URL = 'http://localhost:8080';
const ENDPOINT = '/api/v1/idnot/user'; // Base endpoint, idnot will be added as path parameter
// Test cases for the rattachements endpoint
const testCases = [
{
name: 'Valid IDNot parameter',
idNot: '12345',
expectedStatus: 200,
description: 'Should return rattachements data for valid IDNot'
},
{
name: 'Missing IDNot parameter',
idNot: undefined,
expectedStatus: 404, // Without idnot in path, this should return 404
description: 'Should handle missing IDNot parameter in path'
},
{
name: 'Empty IDNot parameter',
idNot: '',
expectedStatus: 404, // Empty idnot in path should return 404
description: 'Should handle empty IDNot parameter in path'
},
{
name: 'Special characters in IDNot',
idNot: 'test-id_with+special=chars',
expectedStatus: 200,
description: 'Should handle special characters in IDNot'
},
{
name: 'Very long IDNot',
idNot: 'a'.repeat(100),
expectedStatus: 200,
description: 'Should handle very long IDNot values'
}
];
async function testRattachementsEndpoint(testCase) {
console.log(`\n🧪 Testing: ${testCase.name}`);
console.log(`📝 Description: ${testCase.description}`);
console.log(`🆔 IDNot: ${testCase.idNot || 'undefined'}`);
try {
// Build URL with path parameter
let url = `${BASE_URL}${ENDPOINT}`;
if (testCase.idNot !== undefined) {
url += `/${encodeURIComponent(testCase.idNot)}/rattachements`;
} else {
url += '/rattachements'; // Test without idnot parameter
}
console.log(`📍 URL: ${url}`);
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const responseText = await response.text();
let responseData;
try {
responseData = JSON.parse(responseText);
} catch (e) {
responseData = { rawResponse: responseText };
}
console.log(`📊 Status: ${response.status} ${response.statusText}`);
if (response.status === testCase.expectedStatus) {
console.log(`✅ PASS: Expected status ${testCase.expectedStatus}`);
} else {
console.log(`❌ FAIL: Expected status ${testCase.expectedStatus}, got ${response.status}`);
}
// Analyze the response
if (responseData.error) {
console.log(`🚨 Error: ${responseData.error}`);
if (responseData.message) {
console.log(`📄 Message: ${responseData.message}`);
}
} else if (Array.isArray(responseData)) {
console.log(`📋 Response: Array with ${responseData.length} items`);
if (responseData.length > 0) {
console.log(`🔍 First item keys: ${Object.keys(responseData[0]).join(', ')}`);
}
} else if (typeof responseData === 'object') {
console.log(`📋 Response: Object with keys: ${Object.keys(responseData).join(', ')}`);
if (responseData.result) {
console.log(`🔍 Result type: ${Array.isArray(responseData.result) ? 'Array' : typeof responseData.result}`);
}
} else {
console.log(`📋 Response type: ${typeof responseData}`);
console.log(`📄 Content: ${responseText.substring(0, 200)}${responseText.length > 200 ? '...' : ''}`);
}
return {
testCase,
status: response.status,
expectedStatus: testCase.expectedStatus,
passed: response.status === testCase.expectedStatus,
response: responseData,
url: url
};
} catch (error) {
console.log(`💥 Network Error: ${error.message}`);
return {
testCase,
status: 'NETWORK_ERROR',
expectedStatus: testCase.expectedStatus,
passed: false,
error: error.message,
url: url
};
}
}
async function runTests() {
console.log('🚀 Starting Rattachements Endpoint Tests...\n');
console.log(`📍 Testing against: ${BASE_URL}${ENDPOINT}`);
console.log('=' .repeat(70));
const results = [];
for (const testCase of testCases) {
const result = await testRattachementsEndpoint(testCase);
results.push(result);
// Add a small delay between tests
await new Promise(resolve => setTimeout(resolve, 100));
}
// Summary
console.log('\n' + '=' .repeat(70));
console.log('📊 TEST SUMMARY');
console.log('=' .repeat(70));
const passed = results.filter(r => r.passed).length;
const total = results.length;
console.log(`✅ Passed: ${passed}/${total}`);
console.log(`❌ Failed: ${total - passed}/${total}`);
if (passed === total) {
console.log('🎉 All tests passed!');
} else {
console.log('⚠️ Some tests failed. Check the output above for details.');
}
// Failed tests details
const failedTests = results.filter(r => !r.passed);
if (failedTests.length > 0) {
console.log('\n🔍 FAILED TESTS:');
failedTests.forEach(result => {
console.log(` - ${result.testCase.name}: Expected ${result.testCase.expectedStatus}, got ${result.status}`);
});
}
}
// Check if server is running
async function checkServerHealth() {
try {
const response = await fetch(`${BASE_URL}/api/v1/health`);
if (response.ok) {
console.log('✅ Server is running and responding');
return true;
} else {
console.log(`⚠️ Server responded with status: ${response.status}`);
return false;
}
} catch (error) {
console.log('❌ Server is not responding');
console.log('💡 Make sure to start your server first with: npm run dev');
return false;
}
}
// Main execution
async function main() {
console.log('🔍 Checking server health...');
const serverRunning = await checkServerHealth();
if (!serverRunning) {
console.log('❌ Server health check failed. Exiting.');
process.exit(1);
}
console.log('🚀 Starting tests...');
await runTests();
console.log('🏁 Tests completed.');
}
// Handle command line arguments
if (process.argv.includes('--help') || process.argv.includes('-h')) {
console.log(`
Usage: node test-rattachements-endpoint.js [options]
Options:
--help, -h Show this help message
--url <url> Set custom base URL (default: http://localhost:8080)
Examples:
node test-rattachements-endpoint.js
node test-rattachements-endpoint.js --url http://localhost:3000
`);
process.exit(0);
}
// Parse custom URL if provided
const urlIndex = process.argv.indexOf('--url');
if (urlIndex !== -1 && process.argv[urlIndex + 1]) {
BASE_URL = process.argv[urlIndex + 1];
console.log(`🔧 Using custom URL: ${BASE_URL}`);
}
main().catch(console.error);

41
tsconfig.json Normal file
View File

@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true,
"removeComments": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"noImplicitAny": false,
},
"ts-node": {
"compilerOptions": {
"noImplicitAny": false,
"skipLibCheck": true
}
},
"include": [
"src/**/*",
"global.d.ts"
],
"typeRoots": [
"node_modules/@types",
"src"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"test-*.js",
"quick-*.js"
]
}