Checkpoint: Application complète de dématérialisation de facturation avec extraction IA (Mistral), authentification locale + Azure AD, stockage local, et export SFTP.
Fonctionnalités implémentées : ✅ Authentification locale (email/password) + Azure AD + Manus OAuth ✅ Upload drag-and-drop de fichiers PDF avec suivi en temps réel ✅ Extraction automatique avec Mistral AI (OCR + LLM) ✅ Détection de doublons (fournisseur, numéro, date) ✅ Score de qualité d'extraction (0-100) ✅ Tableau de bord avec statistiques ✅ Liste des factures avec recherche et filtres ✅ Paramètres utilisateur (LLM, keywords, SFTP) ✅ Historique des imports avec logs détaillés ✅ Gestion des utilisateurs (admin) ✅ Export SFTP manuel/automatique ✅ Stockage local avec organisation YYYY-MM ✅ Tests unitaires d'authentification Architecture : - Frontend : React 19 + Vite + TailwindCSS + Radix UI - Backend : Express + tRPC + Drizzle ORM - Base de données : MySQL (6 tables) - IA : Mistral AI pour extraction - Stockage : Local filesystem - Export : SFTP Pages : - Login (choix local/Azure/Manus) - Dashboard (statistiques) - Upload (drag-and-drop) - Invoices (liste avec recherche) - Settings (LLM, keywords, SFTP) - History (logs d'import) - Users (gestion admin)
This commit is contained in:
115
server/sftpExport.ts
Normal file
115
server/sftpExport.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import SftpClient from "ssh2-sftp-client";
|
||||
import { localStorageGet } from "./localStorage";
|
||||
import { getUserSettings } from "./db";
|
||||
|
||||
export interface SftpConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
remotePath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SFTP connection
|
||||
*/
|
||||
export async function testSftpConnection(config: SftpConfig): Promise<boolean> {
|
||||
const sftp = new SftpClient();
|
||||
|
||||
try {
|
||||
await sftp.connect({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
});
|
||||
|
||||
console.log("[SFTP] Connection successful");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("[SFTP] Connection failed:", error);
|
||||
return false;
|
||||
} finally {
|
||||
await sftp.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export invoice files (PDF + JSON) to SFTP server
|
||||
*/
|
||||
export async function exportInvoiceToSftp(
|
||||
config: SftpConfig,
|
||||
pdfFileKey: string,
|
||||
jsonFileKey: string | null,
|
||||
invoiceDate: Date
|
||||
): Promise<void> {
|
||||
const sftp = new SftpClient();
|
||||
|
||||
try {
|
||||
// Connect to SFTP
|
||||
await sftp.connect({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
});
|
||||
|
||||
console.log("[SFTP] Connected successfully");
|
||||
|
||||
// Create directory structure: remotePath/YYYY/MM/DD
|
||||
const year = invoiceDate.getFullYear();
|
||||
const month = String(invoiceDate.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(invoiceDate.getDate()).padStart(2, "0");
|
||||
|
||||
const targetDir = `${config.remotePath}/${year}/${month}/${day}`.replace(/\/+/g, "/");
|
||||
|
||||
// Ensure directory exists
|
||||
await sftp.mkdir(targetDir, true);
|
||||
|
||||
console.log(`[SFTP] Created directory: ${targetDir}`);
|
||||
|
||||
// Upload PDF file
|
||||
const pdfBuffer = await localStorageGet(pdfFileKey);
|
||||
const pdfFileName = pdfFileKey.split("/").pop() || "invoice.pdf";
|
||||
const pdfRemotePath = `${targetDir}/${pdfFileName}`;
|
||||
|
||||
await sftp.put(pdfBuffer, pdfRemotePath);
|
||||
console.log(`[SFTP] Uploaded PDF: ${pdfRemotePath}`);
|
||||
|
||||
// Upload JSON metadata file if exists
|
||||
if (jsonFileKey) {
|
||||
const jsonBuffer = await localStorageGet(jsonFileKey);
|
||||
const jsonFileName = jsonFileKey.split("/").pop() || "metadata.json";
|
||||
const jsonRemotePath = `${targetDir}/${jsonFileName}`;
|
||||
|
||||
await sftp.put(jsonBuffer, jsonRemotePath);
|
||||
console.log(`[SFTP] Uploaded JSON: ${jsonRemotePath}`);
|
||||
}
|
||||
|
||||
console.log("[SFTP] Export completed successfully");
|
||||
} catch (error) {
|
||||
console.error("[SFTP] Export failed:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sftp.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SFTP configuration for a user
|
||||
*/
|
||||
export async function getUserSftpConfig(userId: number): Promise<SftpConfig | null> {
|
||||
const settings = await getUserSettings(userId);
|
||||
|
||||
if (!settings || !settings.sftpHost || !settings.sftpUsername || !settings.sftpPassword) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
host: settings.sftpHost,
|
||||
port: settings.sftpPort || 22,
|
||||
username: settings.sftpUsername,
|
||||
password: settings.sftpPassword,
|
||||
remotePath: settings.sftpRemotePath || "/",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user