dolibarr user
This commit is contained in:
parent
df644fd72d
commit
acc7a44165
3
.env
3
.env
@ -9,6 +9,9 @@ KEYCLOAK_BASE_URL=https://connect.slm-lab.net
|
|||||||
KEYCLOAK_ADMIN_USERNAME=alma
|
KEYCLOAK_ADMIN_USERNAME=alma
|
||||||
KEYCLOAK_ADMIN_PASSWORD=PW5jfqX00m
|
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_URL=https://espace.slm-lab.net
|
||||||
NEXTCLOUD_CLIENT_ID=espace.slm-lab.net
|
NEXTCLOUD_CLIENT_ID=espace.slm-lab.net
|
||||||
NEXTCLOUD_CLIENT_SECRET=YHLVMGpu0nGRaP7gMDpSjRr1ia6HiSr1
|
NEXTCLOUD_CLIENT_SECRET=YHLVMGpu0nGRaP7gMDpSjRr1ia6HiSr1
|
||||||
|
|||||||
@ -36,10 +36,17 @@ export async function GET() {
|
|||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!realm) {
|
||||||
|
return NextResponse.json({
|
||||||
|
error: 'Missing Realm',
|
||||||
|
message: 'KEYCLOAK_REALM is required'
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Environment variables check:', envVars);
|
console.log('Environment variables check:', envVars);
|
||||||
|
|
||||||
// Try direct authentication
|
// Try direct authentication using the application realm, not master
|
||||||
const url = `${keycloakUrl}/realms/master/protocol/openid-connect/token`;
|
const url = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`;
|
||||||
const formData = new URLSearchParams();
|
const formData = new URLSearchParams();
|
||||||
|
|
||||||
// Try client credentials if available
|
// Try client credentials if available
|
||||||
@ -47,6 +54,7 @@ export async function GET() {
|
|||||||
formData.append('client_id', clientId);
|
formData.append('client_id', clientId);
|
||||||
formData.append('client_secret', clientSecret);
|
formData.append('client_secret', clientSecret);
|
||||||
formData.append('grant_type', 'client_credentials');
|
formData.append('grant_type', 'client_credentials');
|
||||||
|
console.log('Using client credentials flow');
|
||||||
}
|
}
|
||||||
// Fall back to password grant
|
// Fall back to password grant
|
||||||
else if (adminUsername && adminPassword) {
|
else if (adminUsername && adminPassword) {
|
||||||
@ -54,6 +62,7 @@ export async function GET() {
|
|||||||
formData.append('username', adminUsername);
|
formData.append('username', adminUsername);
|
||||||
formData.append('password', adminPassword);
|
formData.append('password', adminPassword);
|
||||||
formData.append('grant_type', 'password');
|
formData.append('grant_type', 'password');
|
||||||
|
console.log('Using password grant flow');
|
||||||
} else {
|
} else {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
error: 'Missing authentication credentials',
|
error: 'Missing authentication credentials',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { getServerSession } from "next-auth/next";
|
import { getServerSession } from "next-auth/next";
|
||||||
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
import { createDolibarrUser, checkDolibarrUserExists } from "@/lib/dolibarr-api";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
@ -450,11 +451,48 @@ export async function POST(req: Request) {
|
|||||||
// We just log the error and continue
|
// 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({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
user: {
|
user: {
|
||||||
...user,
|
...user,
|
||||||
roles: validRoles,
|
roles: validRoles,
|
||||||
|
dolibarrId: dolibarrUserId, // Include the Dolibarr ID if created
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
38
app/types/dolibarr.ts
Normal file
38
app/types/dolibarr.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
@ -45,7 +45,7 @@ interface MenuItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status, update } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
@ -59,6 +59,13 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
pathname
|
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
|
// Show loading state while session is being checked
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
return null;
|
return null;
|
||||||
@ -212,7 +219,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
icon: Building2,
|
icon: Building2,
|
||||||
href: "/mediation",
|
href: "/mediation",
|
||||||
iframe: process.env.NEXT_PUBLIC_IFRAME_MEDIATIONS_URL,
|
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}
|
height={16.5}
|
||||||
className="text-black"
|
className="text-black"
|
||||||
/>
|
/>
|
||||||
|
{/* Add session refresh button */}
|
||||||
|
<button
|
||||||
|
onClick={refreshSession}
|
||||||
|
className="absolute right-2 top-6 bg-blue-500 text-white rounded-full p-1 hover:bg-blue-600"
|
||||||
|
title="Refresh session data"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Menu Items */}
|
{/* Menu Items */}
|
||||||
@ -296,6 +311,12 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<p>User: {session?.user?.name}</p>
|
<p>User: {session?.user?.name}</p>
|
||||||
<p>Email: {session?.user?.email}</p>
|
<p>Email: {session?.user?.email}</p>
|
||||||
|
<button
|
||||||
|
onClick={refreshSession}
|
||||||
|
className="mt-1 w-full py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Refresh Session Data
|
||||||
|
</button>
|
||||||
<details>
|
<details>
|
||||||
<summary className="cursor-pointer">User Roles</summary>
|
<summary className="cursor-pointer">User Roles</summary>
|
||||||
<pre className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
<pre className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
||||||
|
|||||||
@ -48,6 +48,7 @@ interface User {
|
|||||||
createdTimestamp: number;
|
createdTimestamp: number;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
dolibarrId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Role {
|
interface Role {
|
||||||
@ -513,6 +514,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
|||||||
<TableHead>Email</TableHead>
|
<TableHead>Email</TableHead>
|
||||||
<TableHead>Created At</TableHead>
|
<TableHead>Created At</TableHead>
|
||||||
<TableHead>Roles</TableHead>
|
<TableHead>Roles</TableHead>
|
||||||
|
<TableHead>Dolibarr</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@ -535,6 +537,15 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{user.dolibarrId ? (
|
||||||
|
<span className="inline-flex items-center rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||||
|
ID: {user.dolibarrId}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-400">-</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|||||||
127
lib/dolibarr-api.ts
Normal file
127
lib/dolibarr-api.ts
Normal file
@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
20
types/env.d.ts
vendored
Normal file
20
types/env.d.ts
vendored
Normal file
@ -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 {};
|
||||||
Loading…
Reference in New Issue
Block a user