mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Add configuration interface for enterprise task webhook
Implement a new UI component and backend routes for configuring and testing enterprise task webhooks, including database schema and migration scripts. Replit-Commit-Author: Agent Replit-Commit-Session-Id: cfbfe47c-0c9a-4ebf-8a41-2937407eccff Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/cfbfe47c-0c9a-4ebf-8a41-2937407eccff/vzUkIk6
This commit is contained in:
1 parent
6ce9ec90a4
commit
f938fdb95d
9 files changed
+582
-7
No files matched your search
+141
-2
@@ -68,6 +68,139 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Auth middleware
|
||||
await setupAuth(app);
|
||||
|
||||
// Routes pour configuration webhook BAP
|
||||
app.get('/api/webhook-bap-config', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
// Vérifier que l'utilisateur est admin
|
||||
const userId = req.user?.claims?.sub || req.user?.id;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Utilisateur non authentifié' });
|
||||
}
|
||||
|
||||
const user = await storage.getUser(userId);
|
||||
if (!user || user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Accès refusé - Admin uniquement' });
|
||||
}
|
||||
|
||||
const config = await storage.getWebhookBapConfig();
|
||||
res.json(config || null);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Erreur récupération config webhook BAP:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur', details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/webhook-bap-config', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
// Vérifier que l'utilisateur est admin
|
||||
const userId = req.user?.claims?.sub || req.user?.id;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Utilisateur non authentifié' });
|
||||
}
|
||||
|
||||
const user = await storage.getUser(userId);
|
||||
if (!user || user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Accès refusé - Admin uniquement' });
|
||||
}
|
||||
|
||||
// Valider les données avec Zod
|
||||
const validatedData = insertWebhookBapConfigSchema.parse(req.body);
|
||||
|
||||
// Vérifier si une configuration existe déjà
|
||||
const existingConfig = await storage.getWebhookBapConfig();
|
||||
|
||||
let config: any;
|
||||
if (existingConfig) {
|
||||
// Mettre à jour la configuration existante
|
||||
config = await storage.updateWebhookBapConfig(existingConfig.id, validatedData);
|
||||
} else {
|
||||
// Créer une nouvelle configuration
|
||||
config = await storage.createWebhookBapConfig(validatedData);
|
||||
}
|
||||
|
||||
console.log('✅ Configuration webhook BAP sauvegardée:', {
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
isActive: config.isActive
|
||||
});
|
||||
|
||||
res.json(config);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Erreur sauvegarde config webhook BAP:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur', details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/webhook-bap-config/test', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
// Vérifier que l'utilisateur est admin
|
||||
const userId = req.user?.claims?.sub || req.user?.id;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Utilisateur non authentifié' });
|
||||
}
|
||||
|
||||
const user = await storage.getUser(userId);
|
||||
if (!user || user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Accès refusé - Admin uniquement' });
|
||||
}
|
||||
|
||||
const { webhookUrl } = req.body;
|
||||
if (!webhookUrl) {
|
||||
return res.status(400).json({ error: 'URL webhook requise' });
|
||||
}
|
||||
|
||||
console.log('🔍 Test webhook BAP:', { url: webhookUrl });
|
||||
|
||||
// Tester la connectivité avec le webhook
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 secondes
|
||||
|
||||
const testResponse = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
test: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
message: 'Test de connectivité depuis LogiFlow'
|
||||
}),
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
console.log('🌐 Réponse test webhook:', { status: testResponse.status, ok: testResponse.ok });
|
||||
|
||||
if (!testResponse.ok) {
|
||||
throw new Error(`HTTP ${testResponse.status}: ${testResponse.statusText}`);
|
||||
}
|
||||
|
||||
const result = await testResponse.text();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
status: testResponse.status,
|
||||
message: 'Webhook accessible',
|
||||
response: result.substring(0, 200) // Limiter la réponse
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Erreur test webhook BAP:', error);
|
||||
|
||||
let errorMessage = 'Échec du test de connectivité';
|
||||
if (error.name === 'AbortError') {
|
||||
errorMessage = 'Timeout - Le webhook ne répond pas';
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
res.status(500).json({ error: errorMessage, details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Route BAP pour envoi webhook n8n
|
||||
app.post('/api/bap/send-webhook', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
@@ -143,8 +276,14 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
fileSize: fileBuffer.length
|
||||
});
|
||||
|
||||
// Envoyer vers le webhook n8n
|
||||
const webhookUrl = 'https://workflow.ffnancy.fr/webhook/a3d03176-b72f-412d-8fb9-f920b9fbab4d';
|
||||
// Récupérer l'URL du webhook depuis la configuration
|
||||
const webhookConfig = await storage.getWebhookBapConfig();
|
||||
if (!webhookConfig || !webhookConfig.isActive) {
|
||||
console.error('❌ BAP: Configuration webhook non trouvée ou inactive');
|
||||
return res.status(500).json({ error: 'Configuration webhook BAP non disponible' });
|
||||
}
|
||||
|
||||
const webhookUrl = webhookConfig.webhookUrl;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 secondes
|
||||
|
||||
@@ -66,6 +66,9 @@ import {
|
||||
type InsertWeatherData,
|
||||
type WeatherSettings,
|
||||
type InsertWeatherSettings,
|
||||
webhookBapConfig,
|
||||
type WebhookBapConfig,
|
||||
type InsertWebhookBapConfig,
|
||||
} from "@shared/schema";
|
||||
import { db } from "./db";
|
||||
import { eq, and, inArray, desc, sql, gte, lte, lt, gt, or, isNull, isNotNull, asc, ne } from "drizzle-orm";
|
||||
@@ -219,6 +222,11 @@ export interface IStorage {
|
||||
updateWeatherData(id: number, data: Partial<InsertWeatherData>): Promise<WeatherData>;
|
||||
deleteOldWeatherData(daysToKeep: number): Promise<void>;
|
||||
clearWeatherCache(): Promise<void>;
|
||||
|
||||
// Webhook BAP Configuration
|
||||
getWebhookBapConfig(): Promise<WebhookBapConfig | undefined>;
|
||||
createWebhookBapConfig(config: InsertWebhookBapConfig): Promise<WebhookBapConfig>;
|
||||
updateWebhookBapConfig(id: number, config: Partial<InsertWebhookBapConfig>): Promise<WebhookBapConfig>;
|
||||
}
|
||||
|
||||
export class DatabaseStorage implements IStorage {
|
||||
@@ -2211,6 +2219,29 @@ export class DatabaseStorage implements IStorage {
|
||||
await db.delete(weatherData);
|
||||
console.log('🧹 Weather cache cleared due to location change');
|
||||
}
|
||||
|
||||
// Webhook BAP Configuration
|
||||
async getWebhookBapConfig(): Promise<WebhookBapConfig | undefined> {
|
||||
const [config] = await db.select().from(webhookBapConfig).limit(1);
|
||||
return config;
|
||||
}
|
||||
|
||||
async createWebhookBapConfig(configData: InsertWebhookBapConfig): Promise<WebhookBapConfig> {
|
||||
const [config] = await db
|
||||
.insert(webhookBapConfig)
|
||||
.values(configData)
|
||||
.returning();
|
||||
return config;
|
||||
}
|
||||
|
||||
async updateWebhookBapConfig(id: number, configData: Partial<InsertWebhookBapConfig>): Promise<WebhookBapConfig> {
|
||||
const [config] = await db
|
||||
.update(webhookBapConfig)
|
||||
.set({ ...configData, updatedAt: new Date() })
|
||||
.where(eq(webhookBapConfig.id, id))
|
||||
.returning();
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
// MemStorage class for development
|
||||
@@ -3859,6 +3890,38 @@ export class MemStorage implements IStorage {
|
||||
console.log('🧹 DEV: Weather cache cleared due to location change');
|
||||
}
|
||||
|
||||
// Webhook BAP Configuration
|
||||
async getWebhookBapConfig(): Promise<WebhookBapConfig | undefined> {
|
||||
// En développement, retourner une config par défaut
|
||||
return {
|
||||
id: 1,
|
||||
name: "Configuration BAP",
|
||||
webhookUrl: "https://workflow.ffnancy.fr/webhook/a3d03176-b72f-412d-8fb9-f920b9fbab4d",
|
||||
description: "Configuration par défaut pour développement",
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
async createWebhookBapConfig(config: InsertWebhookBapConfig): Promise<WebhookBapConfig> {
|
||||
return {
|
||||
id: 1,
|
||||
...config,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
async updateWebhookBapConfig(id: number, config: Partial<InsertWebhookBapConfig>): Promise<WebhookBapConfig> {
|
||||
const existing = await this.getWebhookBapConfig();
|
||||
return {
|
||||
...existing!,
|
||||
...config,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
// Announcement operations
|
||||
async createAnnouncement(announcementData: InsertAnnouncement): Promise<Announcement> {
|
||||
const announcementStorage = getAnnouncementStorage(
|
||||
|
||||
Reference in new issue
Block a user