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:
111
server/localStorage.ts
Normal file
111
server/localStorage.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
// Storage base path (local filesystem)
|
||||
const STORAGE_BASE_PATH = process.env.STORAGE_BASE_PATH || path.join(process.cwd(), "storage");
|
||||
|
||||
/**
|
||||
* Ensure storage directory exists
|
||||
*/
|
||||
async function ensureStorageDir(dirPath: string) {
|
||||
try {
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
} catch (error) {
|
||||
console.error(`[LocalStorage] Failed to create directory ${dirPath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a storage key with YYYY-MM prefix for organization
|
||||
*/
|
||||
export function generateStorageKey(userId: number, fileName: string): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const randomId = nanoid(8);
|
||||
|
||||
// Format: YYYY-MM/userId-randomId-filename
|
||||
return `${year}-${month}/${userId}-${randomId}-${fileName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a file in local storage
|
||||
* @param fileKey - Storage key (e.g., "2025-01/1-abc123-invoice.pdf")
|
||||
* @param buffer - File content as Buffer
|
||||
* @param contentType - MIME type (optional, for metadata)
|
||||
* @returns Object with key and public URL
|
||||
*/
|
||||
export async function localStoragePut(
|
||||
fileKey: string,
|
||||
buffer: Buffer,
|
||||
contentType?: string
|
||||
): Promise<{ key: string; url: string }> {
|
||||
try {
|
||||
const fullPath = path.join(STORAGE_BASE_PATH, fileKey);
|
||||
const dirPath = path.dirname(fullPath);
|
||||
|
||||
// Ensure directory exists
|
||||
await ensureStorageDir(dirPath);
|
||||
|
||||
// Write file
|
||||
await fs.writeFile(fullPath, buffer);
|
||||
|
||||
// Generate public URL (served by Express static middleware)
|
||||
const url = `/storage/${fileKey}`;
|
||||
|
||||
console.log(`[LocalStorage] File stored: ${fileKey}`);
|
||||
|
||||
return { key: fileKey, url };
|
||||
} catch (error) {
|
||||
console.error(`[LocalStorage] Failed to store file ${fileKey}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a file from local storage
|
||||
* @param fileKey - Storage key
|
||||
* @returns File content as Buffer
|
||||
*/
|
||||
export async function localStorageGet(fileKey: string): Promise<Buffer> {
|
||||
try {
|
||||
const fullPath = path.join(STORAGE_BASE_PATH, fileKey);
|
||||
const buffer = await fs.readFile(fullPath);
|
||||
return buffer;
|
||||
} catch (error) {
|
||||
console.error(`[LocalStorage] Failed to retrieve file ${fileKey}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from local storage
|
||||
* @param fileKey - Storage key
|
||||
*/
|
||||
export async function localStorageDelete(fileKey: string): Promise<void> {
|
||||
try {
|
||||
const fullPath = path.join(STORAGE_BASE_PATH, fileKey);
|
||||
await fs.unlink(fullPath);
|
||||
console.log(`[LocalStorage] File deleted: ${fileKey}`);
|
||||
} catch (error) {
|
||||
console.error(`[LocalStorage] Failed to delete file ${fileKey}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file exists in storage
|
||||
* @param fileKey - Storage key
|
||||
* @returns true if file exists, false otherwise
|
||||
*/
|
||||
export async function localStorageExists(fileKey: string): Promise<boolean> {
|
||||
try {
|
||||
const fullPath = path.join(STORAGE_BASE_PATH, fileKey);
|
||||
await fs.access(fullPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user