dolibarr user

This commit is contained in:
alma 2025-05-04 10:15:48 +02:00
parent 75854b3b28
commit 9cf463e7fc
4 changed files with 330 additions and 29 deletions

View File

@ -95,6 +95,12 @@ Use these functions for all email formatting needs.
- **Status**: Removed - **Status**: Removed
- **Replacement**: Use `app/api/courrier/send/route.ts` instead. - **Replacement**: Use `app/api/courrier/send/route.ts` instead.
### 4. `DELETE /api/users/[userId]` (DEPRECATED)
- **Status**: Deprecated but maintained for backward compatibility
- **Replacement**: Use `DELETE /api/users?id=[userId]&email=[userEmail]` instead
- **Reason**: The new endpoint format supports deletion across all integrated systems (Keycloak, Leantime, and Dolibarr)
- **Notes**: The deprecated endpoint now forwards requests to the new endpoint but developers should update their code to use the new format directly
## Deprecated Components ## Deprecated Components
### ComposeEmail (components/ComposeEmail.tsx) (REMOVED) ### ComposeEmail (components/ComposeEmail.tsx) (REMOVED)

View File

@ -80,3 +80,29 @@ This centralized approach prevents formatting inconsistencies and direction prob
Several functions have been deprecated and removed in favor of centralized implementations: Several functions have been deprecated and removed in favor of centralized implementations:
- Check the `DEPRECATED_FUNCTIONS.md` file for a complete list of deprecated functions and their replacements. - Check the `DEPRECATED_FUNCTIONS.md` file for a complete list of deprecated functions and their replacements.
## User Management API
The application provides endpoints for managing users in multiple systems:
- **Create User**:
- Endpoint: `POST /api/users`
- Creates users in Keycloak, Leantime, and Dolibarr (if they have "mediation" or "expression" roles)
- **Update User**:
- Endpoint: `PUT /api/users/[userId]`
- Updates user details in Keycloak
- **Delete User**:
- Endpoint: `DELETE /api/users?id=[userId]&email=[userEmail]`
- Deletes users from Keycloak, Leantime, and Dolibarr systems
- **Important**: Always include both `id` and `email` parameters for complete deletion across all systems
- The legacy endpoint `DELETE /api/users/[userId]` forwards to the above endpoint
- **Manage Roles**:
- Endpoint: `PUT /api/users/[userId]/roles`
- Updates user roles in Keycloak
- **Reset Password**:
- Endpoint: `PUT /api/users/[userId]/password`
- Resets user password in Keycloak

View File

@ -174,38 +174,34 @@ export async function DELETE(
const userDetails = await userResponse.json(); const userDetails = await userResponse.json();
console.log('Processing user deletion for ID:', params.userId); console.log('Processing user deletion for ID:', params.userId);
// Delete user from Keycloak // Forward the request to the new endpoint format with the email parameter
const deleteResponse = await fetch( // This ensures Dolibarr deletion is also handled
`${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}`, const apiUrl = new URL(`${req.headers.get('origin') || process.env.NEXTAUTH_URL}/api/users`);
{ apiUrl.searchParams.append('id', params.userId);
apiUrl.searchParams.append('email', userDetails.email);
const forwardResponse = await fetch(apiUrl.toString(), {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokenData.access_token}`, "Cookie": req.headers.get('cookie') || '',
"Authorization": req.headers.get('authorization') || '',
}, },
} });
);
if (!deleteResponse.ok) { if (!forwardResponse.ok) {
console.error("Keycloak delete error"); const errorData = await forwardResponse.json();
console.error("Error forwarding delete request:", errorData);
return NextResponse.json( return NextResponse.json(
{ error: "Erreur lors de la suppression de l'utilisateur" }, { error: "Erreur lors de la suppression de l'utilisateur", details: errorData },
{ status: deleteResponse.status } { status: forwardResponse.status }
); );
} }
// Delete user from Leantime const responseData = await forwardResponse.json();
const leantimeResult = await deleteLeantimeUser(userDetails.email, session.user.id); return NextResponse.json(responseData);
if (!leantimeResult.success) {
console.error("Leantime user deletion failed");
// We don't return an error here since Keycloak user was deleted successfully
// We just log the error and continue
}
return NextResponse.json({ success: true });
} catch (error) { } catch (error) {
console.error("Error deleting user"); console.error("Error deleting user:", error);
return NextResponse.json( return NextResponse.json(
{ error: "Erreur serveur" }, { error: "Erreur serveur" },
{ status: 500 } { status: 500 }

View File

@ -0,0 +1,273 @@
/**
* Test script for verifying user deletion across all integrated systems
*
* This script creates a test user with mediation role, verifies it exists in all systems,
* then deletes it and verifies deletion in all systems.
*
* Usage: node scripts/test-user-deletion.js
*/
require('dotenv').config();
const fetch = require('node-fetch');
// Test user configuration
const TEST_USER = {
username: `test-user-${Date.now()}`,
firstName: 'Test',
lastName: 'User',
email: `test-user-${Date.now()}@example.com`,
password: 'password123',
roles: ['mediation'] // Using mediation role which should be created in Dolibarr
};
// Configuration from environment variables
const config = {
keycloak: {
baseUrl: process.env.KEYCLOAK_BASE_URL,
realm: process.env.KEYCLOAK_REALM,
clientId: process.env.KEYCLOAK_CLIENT_ID,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET
},
leantime: {
apiUrl: 'https://agilite.slm-lab.net/api/jsonrpc',
apiKey: process.env.LEANTIME_TOKEN
},
dolibarr: {
apiUrl: process.env.DOLIBARR_API_URL,
apiKey: process.env.DOLIBARR_API_KEY
},
nextAuthUrl: process.env.NEXTAUTH_URL || 'http://localhost:3000'
};
// Helper to get admin token for Keycloak operations
async function getKeycloakAdminToken() {
const response = await fetch(
`${config.keycloak.baseUrl}/realms/${config.keycloak.realm}/protocol/openid-connect/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: config.keycloak.clientId,
client_secret: config.keycloak.clientSecret,
}),
}
);
const data = await response.json();
if (!response.ok || !data.access_token) {
throw new Error('Failed to get Keycloak admin token');
}
return data.access_token;
}
// Create a test user in all systems via the API
async function createTestUser() {
console.log(`Creating test user: ${TEST_USER.username} (${TEST_USER.email})`);
const response = await fetch(`${config.nextAuthUrl}/api/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(TEST_USER),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to create test user: ${JSON.stringify(error)}`);
}
const data = await response.json();
console.log('User created successfully:', data);
return data.user;
}
// Check if user exists in Keycloak
async function checkKeycloakUser(userId) {
console.log(`Checking if user exists in Keycloak (ID: ${userId})`);
const token = await getKeycloakAdminToken();
const response = await fetch(
`${config.keycloak.baseUrl}/admin/realms/${config.keycloak.realm}/users/${userId}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status === 404) {
console.log('User not found in Keycloak');
return false;
}
if (!response.ok) {
console.error('Error checking Keycloak user:', await response.text());
return null; // Error state
}
console.log('User exists in Keycloak');
return true;
}
// Check if user exists in Leantime
async function checkLeantimeUser(email) {
console.log(`Checking if user exists in Leantime (Email: ${email})`);
try {
const response = await fetch(config.leantime.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': config.leantime.apiKey,
},
body: JSON.stringify({
method: 'leantime.rpc.Users.Users.getUserIdByEmail',
jsonrpc: '2.0',
id: 1,
params: {
email: email
}
})
});
const data = await response.json();
if (!response.ok || !data.result) {
console.log('User not found in Leantime');
return false;
}
console.log('User exists in Leantime');
return true;
} catch (error) {
console.error('Error checking Leantime user:', error);
return null; // Error state
}
}
// Check if user exists in Dolibarr
async function checkDolibarrUser(email) {
console.log(`Checking if user exists in Dolibarr (Email: ${email})`);
try {
const apiUrl = config.dolibarr.apiUrl.endsWith('/')
? config.dolibarr.apiUrl
: `${config.dolibarr.apiUrl}/`;
const response = await fetch(
`${apiUrl}users?sortfield=t.rowid&sortorder=ASC&limit=1&sqlfilters=(t.email:=:'${encodeURIComponent(email)}')`,
{
method: 'GET',
headers: {
'DOLAPIKEY': config.dolibarr.apiKey,
},
}
);
if (!response.ok) {
console.error('Error response from Dolibarr:', await response.text());
return null; // Error state
}
const data = await response.json();
if (!Array.isArray(data) || data.length === 0) {
console.log('User not found in Dolibarr');
return false;
}
console.log('User exists in Dolibarr with ID:', data[0].id);
return { exists: true, id: data[0].id };
} catch (error) {
console.error('Error checking Dolibarr user:', error);
return null; // Error state
}
}
// Delete user from all systems via the API
async function deleteTestUser(userId, email) {
console.log(`Deleting test user: ID=${userId}, Email=${email}`);
const response = await fetch(
`${config.nextAuthUrl}/api/users?id=${userId}&email=${encodeURIComponent(email)}`,
{
method: 'DELETE',
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to delete test user: ${JSON.stringify(error)}`);
}
console.log('User deletion request successful');
return await response.json();
}
// Main test function
async function runTest() {
try {
console.log('=== STARTING USER DELETION TEST ===');
// Step 1: Create a test user
console.log('\n=== Step 1: Creating test user ===');
const createdUser = await createTestUser();
console.log(`Test user created with ID: ${createdUser.id}`);
// Wait a moment for systems to process
console.log('Waiting for systems to process...');
await new Promise(resolve => setTimeout(resolve, 2000));
// Step 2: Verify user exists in all systems
console.log('\n=== Step 2: Verifying user exists in all systems ===');
const keycloakExists = await checkKeycloakUser(createdUser.id);
const leantimeExists = await checkLeantimeUser(TEST_USER.email);
const dolibarrUser = await checkDolibarrUser(TEST_USER.email);
if (keycloakExists === null || leantimeExists === null || dolibarrUser === null) {
throw new Error('Error checking user existence in integrated systems');
}
if (!keycloakExists || !leantimeExists || !dolibarrUser.exists) {
throw new Error('User not created in all systems properly');
}
console.log('User confirmed to exist in all integrated systems');
// Step 3: Delete the test user
console.log('\n=== Step 3: Deleting test user ===');
await deleteTestUser(createdUser.id, TEST_USER.email);
// Wait a moment for systems to process
console.log('Waiting for systems to process deletion...');
await new Promise(resolve => setTimeout(resolve, 2000));
// Step 4: Verify user has been deleted from all systems
console.log('\n=== Step 4: Verifying user deletion from all systems ===');
const keycloakDeleted = !(await checkKeycloakUser(createdUser.id));
const leantimeDeleted = !(await checkLeantimeUser(TEST_USER.email));
const dolibarrDeleted = !(await checkDolibarrUser(TEST_USER.email)).exists;
console.log('\n=== TEST RESULTS ===');
console.log(`Keycloak user deleted: ${keycloakDeleted ? 'YES' : 'NO'}`);
console.log(`Leantime user deleted: ${leantimeDeleted ? 'YES' : 'NO'}`);
console.log(`Dolibarr user deleted: ${dolibarrDeleted ? 'YES' : 'NO'}`);
if (keycloakDeleted && leantimeDeleted && dolibarrDeleted) {
console.log('\n✅ TEST PASSED: User successfully deleted from all systems');
} else {
console.log('\n❌ TEST FAILED: User not deleted from all systems');
}
} catch (error) {
console.error('\n❌ TEST ERROR:', error);
}
console.log('\n=== TEST COMPLETED ===');
}
// Run the test
runTest();