mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-22 14:38:19 +02:00
Prevent duplicate user entries by handling empty emails
Fixes an issue where creating or editing users with empty email fields would violate a unique constraint in the database. The solution involves updating user routes to treat empty email strings as NULL, adjusting validation schemas, and providing a script to clean existing data. Replit-Commit-Author: Agent Replit-Commit-Session-Id: a8a78c07-e900-425c-a577-5b4c5894379d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/a8a78c07-e900-425c-a577-5b4c5894379d/zoO1uWT
This commit is contained in:
1 parent
c77ea88e9d
commit
6a2e5aad05
5 files changed
+72
-21
No files matched your search
@@ -54,7 +54,11 @@ Si l'interface affiche "Aucun utilisateur trouvé" :
|
|||||||
### Problème Création d'Utilisateur
|
### Problème Création d'Utilisateur
|
||||||
|
|
||||||
#### Erreur "duplicate key value violates unique constraint users_email_key"
|
#### 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** :
|
**Solution** :
|
||||||
1. Exécutez le script de correction :
|
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
|
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
|
#### Tests de Création
|
||||||
1. **Test Manuel** : Utilisez `test-create-user-production.js`
|
1. **Test Manuel** : Utilisez `test-create-user-production.js`
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 346 KiB |
@@ -140,3 +140,13 @@ Preferred communication style: Simple, everyday language.
|
|||||||
- **Root Cause**: Dynamic imports in production needed `.js` extension for compiled files
|
- **Root Cause**: Dynamic imports in production needed `.js` extension for compiled files
|
||||||
- **Solution**: Updated import paths to use `./localAuth.production.js` for production environment
|
- **Solution**: Updated import paths to use `./localAuth.production.js` for production environment
|
||||||
- **Result**: Password hashing now works correctly in production for user updates
|
- **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
|
||||||
+37
-10
@@ -1001,8 +1001,12 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
app.put('/api/users/:id', isAuthenticated, async (req: any, res) => {
|
app.put('/api/users/:id', isAuthenticated, async (req: any, res) => {
|
||||||
try {
|
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);
|
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
|
||||||
if (!user || user.role !== 'admin') {
|
if (!user || user.role !== 'admin') {
|
||||||
|
console.log('❌ Access denied - user not admin');
|
||||||
return res.status(403).json({ message: "Access denied" });
|
return res.status(403).json({ message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,26 +1014,49 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
const updateUserSchema = z.object({
|
const updateUserSchema = z.object({
|
||||||
username: z.string().optional(),
|
username: z.string().optional(),
|
||||||
role: z.enum(['admin', 'directeur', 'manager', 'employee']).optional(),
|
role: z.enum(['admin', 'directeur', 'manager', 'employee']).optional(),
|
||||||
firstName: z.union([z.string(), z.literal("")]).optional(),
|
firstName: z.union([z.string(), z.literal(""), z.null()]).optional(),
|
||||||
lastName: z.union([z.string(), z.literal("")]).optional(),
|
lastName: z.union([z.string(), z.literal(""), z.null()]).optional(),
|
||||||
email: z.union([z.string().email(), z.literal("")]).optional(),
|
email: z.union([z.string().email(), z.literal(""), z.null()]).optional(),
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('🔍 Parsing update data...');
|
||||||
const userData = updateUserSchema.parse(req.body);
|
const userData = updateUserSchema.parse(req.body);
|
||||||
|
console.log('✅ Update data parsed successfully');
|
||||||
|
|
||||||
// Hash password if provided
|
// Clean up the data - handle empty emails properly
|
||||||
if (userData.password) {
|
const cleanUserData: any = { ...userData };
|
||||||
userData.password = await hashPasswordSimple(userData.password);
|
|
||||||
// Mark password as changed
|
// Handle email field - convert empty string to null
|
||||||
(userData as any).passwordChanged = true;
|
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);
|
res.json(updatedUser);
|
||||||
} catch (error: any) {
|
} 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) {
|
if (error instanceof z.ZodError) {
|
||||||
return res.status(400).json({ message: "Invalid user data", errors: error.errors });
|
return res.status(400).json({ message: "Invalid user data", errors: error.errors });
|
||||||
|
|||||||
+19
-9
@@ -317,15 +317,25 @@ export class DatabaseStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(id: string, userData: Partial<UpsertUser>): Promise<User> {
|
async updateUser(id: string, userData: Partial<UpsertUser>): Promise<User> {
|
||||||
const [user] = await db
|
console.log('🔍 Storage updateUser called for:', id);
|
||||||
.update(users)
|
console.log('🔍 Update data:', userData);
|
||||||
.set({
|
try {
|
||||||
...userData,
|
const [user] = await db
|
||||||
updatedAt: new Date(),
|
.update(users)
|
||||||
})
|
.set({
|
||||||
.where(eq(users.id, id))
|
...userData,
|
||||||
.returning();
|
updatedAt: new Date(),
|
||||||
return user;
|
})
|
||||||
|
.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<void> {
|
async deleteUser(id: string): Promise<void> {
|
||||||
|
|||||||
Reference in new issue
Block a user