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_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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
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) {
|
||||
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 */}
|
||||
<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>
|
||||
|
||||
{/* Menu Items */}
|
||||
@ -296,6 +311,12 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||
<div className="mt-1">
|
||||
<p>User: {session?.user?.name}</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>
|
||||
<summary className="cursor-pointer">User Roles</summary>
|
||||
<pre className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
||||
|
||||
@ -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) {
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Created At</TableHead>
|
||||
<TableHead>Roles</TableHead>
|
||||
<TableHead>Dolibarr</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@ -535,6 +537,15 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
||||
))}
|
||||
</div>
|
||||
</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">
|
||||
<DropdownMenu>
|
||||
<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