From acc7a44165beef23ff49de7ee45036b48958a80b Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 4 May 2025 09:23:26 +0200 Subject: [PATCH] dolibarr user --- .env | 3 + app/api/auth/debug-keycloak/route.ts | 13 ++- app/api/users/route.ts | 38 ++++++++ app/types/dolibarr.ts | 38 ++++++++ components/sidebar.tsx | 25 +++++- components/users/users-table.tsx | 11 +++ lib/dolibarr-api.ts | 127 +++++++++++++++++++++++++++ types/env.d.ts | 20 +++++ 8 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 app/types/dolibarr.ts create mode 100644 lib/dolibarr-api.ts create mode 100644 types/env.d.ts diff --git a/.env b/.env index 981d2bf5..462eba20 100644 --- a/.env +++ b/.env @@ -9,6 +9,9 @@ KEYCLOAK_BASE_URL=https://connect.slm-lab.net KEYCLOAK_ADMIN_USERNAME=alma KEYCLOAK_ADMIN_PASSWORD=PW5jfqX00m +DOLIBARR_API_URL=https://mediations.slm-lab.net/api/index.php/ +DOLIBARR_API_KEY=2znq976PzZz1q2JSe9DG2A3hmbNMGIh8 + NEXTCLOUD_URL=https://espace.slm-lab.net NEXTCLOUD_CLIENT_ID=espace.slm-lab.net NEXTCLOUD_CLIENT_SECRET=YHLVMGpu0nGRaP7gMDpSjRr1ia6HiSr1 diff --git a/app/api/auth/debug-keycloak/route.ts b/app/api/auth/debug-keycloak/route.ts index 31030b56..88c320f0 100644 --- a/app/api/auth/debug-keycloak/route.ts +++ b/app/api/auth/debug-keycloak/route.ts @@ -36,10 +36,17 @@ export async function GET() { }, { status: 400 }); } + if (!realm) { + return NextResponse.json({ + error: 'Missing Realm', + message: 'KEYCLOAK_REALM is required' + }, { status: 400 }); + } + console.log('Environment variables check:', envVars); - // Try direct authentication - const url = `${keycloakUrl}/realms/master/protocol/openid-connect/token`; + // Try direct authentication using the application realm, not master + const url = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`; const formData = new URLSearchParams(); // Try client credentials if available @@ -47,6 +54,7 @@ export async function GET() { formData.append('client_id', clientId); formData.append('client_secret', clientSecret); formData.append('grant_type', 'client_credentials'); + console.log('Using client credentials flow'); } // Fall back to password grant else if (adminUsername && adminPassword) { @@ -54,6 +62,7 @@ export async function GET() { formData.append('username', adminUsername); formData.append('password', adminPassword); formData.append('grant_type', 'password'); + console.log('Using password grant flow'); } else { return NextResponse.json({ error: 'Missing authentication credentials', diff --git a/app/api/users/route.ts b/app/api/users/route.ts index d3fd2f58..1583ed32 100644 --- a/app/api/users/route.ts +++ b/app/api/users/route.ts @@ -1,6 +1,7 @@ import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { NextResponse } from "next/server"; +import { createDolibarrUser, checkDolibarrUserExists } from "@/lib/dolibarr-api"; export async function GET() { const session = await getServerSession(authOptions); @@ -450,11 +451,48 @@ export async function POST(req: Request) { // We just log the error and continue } + // Check if the user has mediation or expression role and create in Dolibarr if needed + const hasMediationRole = validRoles.includes('mediation'); + const hasExpressionRole = validRoles.includes('expression'); + + let dolibarrUserId = null; + + if (hasMediationRole || hasExpressionRole) { + console.log(`User has special role (mediation: ${hasMediationRole}, expression: ${hasExpressionRole}), creating in Dolibarr`); + + // First check if the user already exists in Dolibarr + const existingUser = await checkDolibarrUserExists(data.email); + + if (existingUser.exists) { + console.log(`User already exists in Dolibarr with ID: ${existingUser.id}`); + dolibarrUserId = existingUser.id; + } else { + // Create user in Dolibarr + const dolibarrResult = await createDolibarrUser({ + username: data.username, + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + password: data.password, + }); + + if (dolibarrResult.success) { + console.log(`User created in Dolibarr with ID: ${dolibarrResult.id}`); + dolibarrUserId = dolibarrResult.id; + } else { + console.error("Dolibarr user creation failed:", dolibarrResult.error); + // We don't return an error here since Keycloak user was created successfully + // We just log the error and continue + } + } + } + return NextResponse.json({ success: true, user: { ...user, roles: validRoles, + dolibarrId: dolibarrUserId, // Include the Dolibarr ID if created }, }); diff --git a/app/types/dolibarr.ts b/app/types/dolibarr.ts new file mode 100644 index 00000000..a6fa086e --- /dev/null +++ b/app/types/dolibarr.ts @@ -0,0 +1,38 @@ +/** + * Dolibarr API response types + */ + +export interface DolibarrErrorResponse { + error: { + code: string; + message: string; + }; +} + +export interface DolibarrThirdParty { + id: number; + name: string; + name_alias?: string; + email?: string; + phone?: string; + address?: string; + zip?: string; + town?: string; + status: number; + client: number; + code_client: string; + date_creation: string; + date_modification?: string; +} + +export interface DolibarrUser { + id: number; + login: string; + lastname?: string; + firstname?: string; + email?: string; + status: number; + admin: number; + entity: number; + employee: number; +} \ No newline at end of file diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 81265f88..cdfbfe5c 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -45,7 +45,7 @@ interface MenuItem { } export function Sidebar({ isOpen, onClose }: SidebarProps) { - const { data: session, status } = useSession(); + const { data: session, status, update } = useSession(); const router = useRouter(); const pathname = usePathname(); @@ -59,6 +59,13 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { pathname }); + // Function to manually refresh the session from the server + const refreshSession = async () => { + console.log('Manually refreshing session...'); + await update(); // Force update the session + console.log('Session refreshed!'); + }; + // Show loading state while session is being checked if (status === 'loading') { return null; @@ -212,7 +219,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { icon: Building2, href: "/mediation", iframe: process.env.NEXT_PUBLIC_IFRAME_MEDIATIONS_URL, - requiredRole: ["mediation", "expression"], + requiredRole: "mediation", }, ]; @@ -270,6 +277,14 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { height={16.5} className="text-black" /> + {/* Add session refresh button */} + {/* Menu Items */} @@ -296,6 +311,12 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {

User: {session?.user?.name}

Email: {session?.user?.email}

+
User Roles
diff --git a/components/users/users-table.tsx b/components/users/users-table.tsx
index 7de49f53..6fcf1781 100644
--- a/components/users/users-table.tsx
+++ b/components/users/users-table.tsx
@@ -48,6 +48,7 @@ interface User {
   createdTimestamp: number;
   roles: string[];
   enabled: boolean;
+  dolibarrId?: number;
 }
 
 interface Role {
@@ -513,6 +514,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
             Email
             Created At
             Roles
+            Dolibarr
             Actions
           
         
@@ -535,6 +537,15 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
                   ))}
                 
+ + {user.dolibarrId ? ( + + ID: {user.dolibarrId} + + ) : ( + - + )} + diff --git a/lib/dolibarr-api.ts b/lib/dolibarr-api.ts new file mode 100644 index 00000000..c438ed6b --- /dev/null +++ b/lib/dolibarr-api.ts @@ -0,0 +1,127 @@ +import { NextResponse } from "next/server"; +import { DolibarrErrorResponse, DolibarrThirdParty } from "@/app/types/dolibarr"; + +/** + * Create a user in Dolibarr + * @param userData User data to create in Dolibarr + * @returns Response with success status and ID or error + */ +export async function createDolibarrUser(userData: { + username: string; + firstName: string; + lastName: string; + email: string; + password: string; +}): Promise<{ success: boolean; id?: number; error?: string }> { + try { + // Validate environment variables + if (!process.env.DOLIBARR_API_URL) { + console.error('Missing DOLIBARR_API_URL environment variable'); + return { success: false, error: 'Dolibarr API URL not configured' }; + } + + if (!process.env.DOLIBARR_API_KEY) { + console.error('Missing DOLIBARR_API_KEY environment variable'); + return { success: false, error: 'Dolibarr API key not configured' }; + } + + // Format the API URL properly + const apiUrl = process.env.DOLIBARR_API_URL.endsWith('/') + ? process.env.DOLIBARR_API_URL + : `${process.env.DOLIBARR_API_URL}/`; + + console.log(`Creating Dolibarr user for ${userData.email}`); + + // Create the user in Dolibarr as a thirdparty/customer + const response = await fetch(`${apiUrl}thirdparties`, { + method: 'POST', + headers: { + 'DOLAPIKEY': process.env.DOLIBARR_API_KEY, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: `${userData.firstName} ${userData.lastName}`, + name_alias: userData.username, + email: userData.email, + client: '1', // Mark as customer + code_client: 'auto', // Auto-generate client code + note_private: 'Created via API integration from platform', + status: '1', // Active + }), + }); + + // Handle non-OK responses + if (!response.ok) { + let errorMessage = `HTTP error ${response.status}`; + + try { + const errorData = await response.json() as DolibarrErrorResponse; + errorMessage = errorData.error?.message || errorMessage; + console.error('Dolibarr API error response:', errorData); + } catch (jsonError) { + console.error('Failed to parse Dolibarr error response'); + } + + return { + success: false, + error: `Failed to create Dolibarr user: ${errorMessage}` + }; + } + + // Parse the successful response + const data = await response.json(); + console.log('Dolibarr user created successfully with ID:', data); + + return { success: true, id: data }; + } catch (error) { + console.error('Error creating Dolibarr user:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }; + } +} + +/** + * Check if a user exists in Dolibarr by email + * @param email Email to search for + * @returns Boolean indicating if user exists and user ID if found + */ +export async function checkDolibarrUserExists(email: string): Promise<{ exists: boolean; id?: number }> { + try { + if (!process.env.DOLIBARR_API_URL || !process.env.DOLIBARR_API_KEY) { + console.error('Missing Dolibarr API configuration'); + return { exists: false }; + } + + // Format the API URL + const apiUrl = process.env.DOLIBARR_API_URL.endsWith('/') + ? process.env.DOLIBARR_API_URL + : `${process.env.DOLIBARR_API_URL}/`; + + // Search for thirdparty/customer with matching email + const response = await fetch(`${apiUrl}thirdparties?sortfield=t.rowid&sortorder=ASC&limit=1&sqlfilters=(t.email:=:'${encodeURIComponent(email)}')`, { + method: 'GET', + headers: { + 'DOLAPIKEY': process.env.DOLIBARR_API_KEY, + }, + }); + + if (!response.ok) { + console.error(`Error checking if Dolibarr user exists: HTTP ${response.status}`); + return { exists: false }; + } + + const data = await response.json() as DolibarrThirdParty[]; + + // If we got results, user exists + if (Array.isArray(data) && data.length > 0) { + return { exists: true, id: data[0].id }; + } + + return { exists: false }; + } catch (error) { + console.error('Error checking if Dolibarr user exists:', error); + return { exists: false }; + } +} \ No newline at end of file diff --git a/types/env.d.ts b/types/env.d.ts new file mode 100644 index 00000000..19dae4a3 --- /dev/null +++ b/types/env.d.ts @@ -0,0 +1,20 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + // Existing env vars + KEYCLOAK_BASE_URL: string; + KEYCLOAK_REALM: string; + KEYCLOAK_CLIENT_ID: string; + KEYCLOAK_CLIENT_SECRET: string; + KEYCLOAK_ADMIN_USERNAME?: string; + KEYCLOAK_ADMIN_PASSWORD?: string; + LEANTIME_TOKEN?: string; + + // Dolibarr API env vars + DOLIBARR_API_URL: string; + DOLIBARR_API_KEY: string; + } + } +} + +export {}; \ No newline at end of file