diff --git a/PRODUCTION-FIX-GUIDE.md b/PRODUCTION-FIX-GUIDE.md index 5875fe1..088332d 100644 --- a/PRODUCTION-FIX-GUIDE.md +++ b/PRODUCTION-FIX-GUIDE.md @@ -54,7 +54,11 @@ Si l'interface affiche "Aucun utilisateur trouvé" : ### Problème Création d'Utilisateur #### Erreur "duplicate key value violates unique constraint users_email_key" -Cette erreur survient quand plusieurs utilisateurs ont un email vide (`""`) car PostgreSQL traite les chaînes vides comme des valeurs uniques. +Cette erreur survient lors de la **création** ou **édition** d'utilisateur quand plusieurs utilisateurs ont un email vide (`""`) car PostgreSQL traite les chaînes vides comme des valeurs uniques. + +**Symptômes** : +- Création d'utilisateur échoue avec erreur `Key (email)=() already exists` +- Édition d'utilisateur échoue avec la même erreur lors de la sauvegarde **Solution** : 1. Exécutez le script de correction : @@ -62,7 +66,7 @@ Cette erreur survient quand plusieurs utilisateurs ont un email vide (`""`) car psql $DATABASE_URL -f fix-duplicate-empty-email.sql ``` -2. Redéployez l'application (le code a été corrigé pour utiliser `NULL` au lieu de `""`) +2. Redéployez l'application (le code a été corrigé pour utiliser `NULL` au lieu de `""` dans les routes de création ET d'édition) #### Tests de Création 1. **Test Manuel** : Utilisez `test-create-user-production.js` diff --git a/attached_assets/image_1754943064930.png b/attached_assets/image_1754943064930.png new file mode 100644 index 0000000..965c97b Binary files /dev/null and b/attached_assets/image_1754943064930.png differ diff --git a/replit.md b/replit.md index 52b91df..3331430 100644 --- a/replit.md +++ b/replit.md @@ -139,4 +139,14 @@ Preferred communication style: Simple, everyday language. - **Issue**: Admin password changes failed in production with ERR_MODULE_NOT_FOUND for localAuth.production - **Root Cause**: Dynamic imports in production needed `.js` extension for compiled files - **Solution**: Updated import paths to use `./localAuth.production.js` for production environment -- **Result**: Password hashing now works correctly in production for user updates \ No newline at end of file +- **Result**: Password hashing now works correctly in production for user updates + +#### Email Constraint Fix for User Management +- **Issue**: Both user creation and editing failed with "duplicate key value violates unique constraint users_email_key" error +- **Root Cause**: PostgreSQL treats empty strings (`""`) as unique values, but multiple users with empty emails violated uniqueness constraint +- **Solution Implemented**: + - Updated both POST `/api/users` (creation) and PUT `/api/users/:id` (editing) routes to convert empty emails to `NULL` + - Enhanced validation schemas to accept `null` values for email fields + - Created `fix-duplicate-empty-email.sql` script to clean existing database records + - Improved error handling and logging for better production debugging +- **Result**: Users can now be created and edited with optional email fields without constraint violations \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index 6c7a5b1..04ae466 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1001,8 +1001,12 @@ export async function registerRoutes(app: Express): Promise { app.put('/api/users/:id', isAuthenticated, async (req: any, res) => { try { + console.log('🔍 PUT /api/users/:id - Updating user:', req.params.id); + console.log('📥 Update data:', JSON.stringify(req.body, null, 2)); + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); if (!user || user.role !== 'admin') { + console.log('❌ Access denied - user not admin'); return res.status(403).json({ message: "Access denied" }); } @@ -1010,26 +1014,49 @@ export async function registerRoutes(app: Express): Promise { const updateUserSchema = z.object({ username: z.string().optional(), role: z.enum(['admin', 'directeur', 'manager', 'employee']).optional(), - firstName: z.union([z.string(), z.literal("")]).optional(), - lastName: z.union([z.string(), z.literal("")]).optional(), - email: z.union([z.string().email(), z.literal("")]).optional(), + firstName: z.union([z.string(), z.literal(""), z.null()]).optional(), + lastName: z.union([z.string(), z.literal(""), z.null()]).optional(), + email: z.union([z.string().email(), z.literal(""), z.null()]).optional(), password: z.string().optional(), }); + console.log('🔍 Parsing update data...'); const userData = updateUserSchema.parse(req.body); + console.log('✅ Update data parsed successfully'); - // Hash password if provided - if (userData.password) { - userData.password = await hashPasswordSimple(userData.password); - // Mark password as changed - (userData as any).passwordChanged = true; + // Clean up the data - handle empty emails properly + const cleanUserData: any = { ...userData }; + + // Handle email field - convert empty string to null + if (cleanUserData.email !== undefined) { + cleanUserData.email = cleanUserData.email && cleanUserData.email.trim() !== '' ? cleanUserData.email : null; + console.log('🔍 Email field processed:', cleanUserData.email === null ? 'NULL' : cleanUserData.email); } - const updatedUser = await storage.updateUser(req.params.id, userData); + // Hash password if provided + if (cleanUserData.password) { + try { + console.log('🔒 Hashing password...'); + cleanUserData.password = await hashPasswordSimple(cleanUserData.password); + cleanUserData.passwordChanged = true; + console.log('✅ Password hashed successfully'); + } catch (hashError) { + console.error('❌ Password hashing failed:', hashError); + return res.status(500).json({ message: "Failed to secure password" }); + } + } + + console.log('🔍 Updating user in database...'); + const updatedUser = await storage.updateUser(req.params.id, cleanUserData); + console.log('✅ User updated successfully:', updatedUser.username); res.json(updatedUser); } catch (error: any) { - console.error("Error updating user:", error); + console.error("❌ Error updating user:", error); + console.error("❌ Error type:", error.constructor.name); + console.error("❌ Error code:", error.code); + console.error("❌ Error constraint:", error.constraint); + console.error("❌ Error stack:", error.stack); if (error instanceof z.ZodError) { return res.status(400).json({ message: "Invalid user data", errors: error.errors }); diff --git a/server/storage.ts b/server/storage.ts index 333218c..5bbc53a 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -317,15 +317,25 @@ export class DatabaseStorage implements IStorage { } async updateUser(id: string, userData: Partial): Promise { - const [user] = await db - .update(users) - .set({ - ...userData, - updatedAt: new Date(), - }) - .where(eq(users.id, id)) - .returning(); - return user; + console.log('🔍 Storage updateUser called for:', id); + console.log('🔍 Update data:', userData); + try { + const [user] = await db + .update(users) + .set({ + ...userData, + updatedAt: new Date(), + }) + .where(eq(users.id, id)) + .returning(); + console.log('✅ Storage updateUser successful:', user.username); + return user; + } catch (error) { + console.error('❌ Storage updateUser error:', error); + console.error('❌ Storage error code:', error.code); + console.error('❌ Storage error constraint:', error.constraint); + throw error; + } } async deleteUser(id: string): Promise {