Compare commits
2
Commits
bf8b4c387e
...
882a1949a1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
882a1949a1 | ||
|
|
78a26fe970 |
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
trigger: always_on
|
trigger: always_on
|
||||||
glob:
|
|
||||||
description:
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Per affrontare una issue di git tieni in considerazione anche i commenti interni.
|
||||||
@@ -487,6 +487,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Topbar ---- */
|
/* ---- Topbar ---- */
|
||||||
@@ -568,6 +569,7 @@ body {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding: var(--space-xl);
|
padding: var(--space-xl);
|
||||||
animation: fadeIn var(--transition-base);
|
animation: fadeIn var(--transition-base);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|||||||
@@ -223,6 +223,9 @@ const App = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Sync LDAP customer users to local DB cache
|
||||||
|
await this.api('/api/customer-users/sync', { method: 'POST' });
|
||||||
|
|
||||||
await this.ensureLookups(true);
|
await this.ensureLookups(true);
|
||||||
await this.initAgentSelector();
|
await this.initAgentSelector();
|
||||||
Toast.success('Dati locali (code, utenti, ecc.) aggiornati con successo!');
|
Toast.success('Dati locali (code, utenti, ecc.) aggiornati con successo!');
|
||||||
|
|||||||
@@ -314,7 +314,6 @@ const TicketCreateView = {
|
|||||||
document.getElementById('create-company-search').value = this.savedState.company_search;
|
document.getElementById('create-company-search').value = this.savedState.company_search;
|
||||||
document.getElementById('create-priority').value = this.savedState.priority_id;
|
document.getElementById('create-priority').value = this.savedState.priority_id;
|
||||||
document.getElementById('create-subject').value = this.savedState.subject;
|
document.getElementById('create-subject').value = this.savedState.subject;
|
||||||
document.getElementById('create-body').value = this.savedState.body;
|
|
||||||
|
|
||||||
if (this.savedState.isAdvancedVisible) {
|
if (this.savedState.isAdvancedVisible) {
|
||||||
advancedOptions.style.display = 'grid';
|
advancedOptions.style.display = 'grid';
|
||||||
@@ -388,11 +387,16 @@ const TicketCreateView = {
|
|||||||
userSearchInput.addEventListener('input', () => {
|
userSearchInput.addEventListener('input', () => {
|
||||||
clearTimeout(userDebounce);
|
clearTimeout(userDebounce);
|
||||||
const q = userSearchInput.value.trim();
|
const q = userSearchInput.value.trim();
|
||||||
// Do not block empty query to allow all results on focus
|
|
||||||
|
|
||||||
userDebounce = setTimeout(async () => {
|
userDebounce = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
|
const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
|
||||||
|
console.log('[Frontend LDAP Search] Users returned:', users);
|
||||||
|
// Prevent race conditions: discard results if the input value has changed
|
||||||
|
if (userSearchInput.value.trim() !== q) {
|
||||||
|
console.log('[Frontend LDAP Search] Discarding stale results for query:', q);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
userSuggestionsDiv.innerHTML = '<div class="autocomplete-suggestion-item" style="color:var(--text-muted); cursor:default;">Nessun utente trovato</div>';
|
userSuggestionsDiv.innerHTML = '<div class="autocomplete-suggestion-item" style="color:var(--text-muted); cursor:default;">Nessun utente trovato</div>';
|
||||||
userSuggestionsDiv.style.display = 'block';
|
userSuggestionsDiv.style.display = 'block';
|
||||||
@@ -400,17 +404,18 @@ const TicketCreateView = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userSuggestionsDiv.innerHTML = users.map(u => `
|
userSuggestionsDiv.innerHTML = users.map(u => `
|
||||||
<div class="autocomplete-suggestion-item" data-login="${App.escapeHtml(u.login)}" data-customer-id="${App.escapeHtml(u.customer_id || '')}" data-name="${App.escapeHtml(u.first_name + ' ' + u.last_name)}">
|
<div class="autocomplete-suggestion-item" data-login="${App.escapeHtml(u.login || '')}" data-customer-id="${App.escapeHtml(u.customer_id || '')}" data-name="${App.escapeHtml((u.first_name + ' ' + u.last_name).trim() || u.login || '')}">
|
||||||
<strong>${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}</strong>
|
<strong>${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}</strong>
|
||||||
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:var(--space-xs); font-weight:normal;">(Login: ${App.escapeHtml(u.login)} | Azienda: ${App.escapeHtml(u.customer_id || '—')})</span>
|
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:var(--space-xs); font-weight:normal;">(Login: ${App.escapeHtml(u.login || '')} | Azienda: ${App.escapeHtml(u.customer_id || '—')})</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
userSuggestionsDiv.style.display = 'block';
|
userSuggestionsDiv.style.display = 'block';
|
||||||
|
|
||||||
// Bind click
|
// Use mousedown instead of click to fire before blur event
|
||||||
userSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
|
userSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
|
||||||
if (item.dataset.login) {
|
if (item.dataset.login) {
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault(); // prevent input from losing focus before value is set
|
||||||
userSearchInput.value = item.dataset.name;
|
userSearchInput.value = item.dataset.name;
|
||||||
customerUserIdInput.value = item.dataset.login;
|
customerUserIdInput.value = item.dataset.login;
|
||||||
userSuggestionsDiv.style.display = 'none';
|
userSuggestionsDiv.style.display = 'none';
|
||||||
@@ -429,12 +434,18 @@ const TicketCreateView = {
|
|||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
userSearchInput.addEventListener('focus', () => {
|
userSearchInput.addEventListener('focus', () => {
|
||||||
userSearchInput.value = '';
|
if (!customerUserIdInput.value) {
|
||||||
customerUserIdInput.value = '';
|
// Only search if no user is selected yet
|
||||||
userSearchInput.dispatchEvent(new Event('input'));
|
userSearchInput.dispatchEvent(new Event('input'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
userSearchInput.addEventListener('blur', () => {
|
||||||
|
// Small delay to allow mousedown on item to fire first
|
||||||
|
setTimeout(() => { userSuggestionsDiv.style.display = 'none'; }, 150);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Owner Autocomplete (dynamic backend search)
|
// Owner Autocomplete (dynamic backend search)
|
||||||
let ownerDebounce;
|
let ownerDebounce;
|
||||||
if (ownerSearchInput) {
|
if (ownerSearchInput) {
|
||||||
|
|||||||
@@ -455,11 +455,16 @@ const TicketDetailView = {
|
|||||||
customerSearchInput.addEventListener('input', () => {
|
customerSearchInput.addEventListener('input', () => {
|
||||||
clearTimeout(customerDebounce);
|
clearTimeout(customerDebounce);
|
||||||
const q = customerSearchInput.value.trim();
|
const q = customerSearchInput.value.trim();
|
||||||
// Do not block empty query to allow all results on focus
|
|
||||||
|
|
||||||
customerDebounce = setTimeout(async () => {
|
customerDebounce = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
|
const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
|
||||||
|
console.log('[Frontend LDAP Search Detail] Users returned:', users);
|
||||||
|
// Prevent race conditions: discard results if the input value has changed
|
||||||
|
if (customerSearchInput.value.trim() !== q) {
|
||||||
|
console.log('[Frontend LDAP Search Detail] Discarding stale results for query:', q);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
customerSuggestionsDiv.innerHTML = '<div class="autocomplete-suggestion-item" style="color:var(--text-muted); cursor:default;">Nessun utente trovato</div>';
|
customerSuggestionsDiv.innerHTML = '<div class="autocomplete-suggestion-item" style="color:var(--text-muted); cursor:default;">Nessun utente trovato</div>';
|
||||||
customerSuggestionsDiv.style.display = 'block';
|
customerSuggestionsDiv.style.display = 'block';
|
||||||
@@ -467,17 +472,18 @@ const TicketDetailView = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
customerSuggestionsDiv.innerHTML = users.map(u => `
|
customerSuggestionsDiv.innerHTML = users.map(u => `
|
||||||
<div class="autocomplete-suggestion-item" data-login="${App.escapeHtml(u.login)}" data-customer-id="${App.escapeHtml(u.customer_id || '')}" data-name="${App.escapeHtml(u.first_name + ' ' + u.last_name)}">
|
<div class="autocomplete-suggestion-item" data-login="${App.escapeHtml(u.login || '')}" data-customer-id="${App.escapeHtml(u.customer_id || '')}" data-name="${App.escapeHtml((u.first_name + ' ' + u.last_name).trim() || u.login || '')}">
|
||||||
<strong>${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}</strong>
|
<strong>${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}</strong>
|
||||||
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:var(--space-xs); font-weight:normal;">(Login: ${App.escapeHtml(u.login)} | Azienda: ${App.escapeHtml(u.customer_id || '—')})</span>
|
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:var(--space-xs); font-weight:normal;">(Login: ${App.escapeHtml(u.login || '')} | Azienda: ${App.escapeHtml(u.customer_id || '—')})</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
customerSuggestionsDiv.style.display = 'block';
|
customerSuggestionsDiv.style.display = 'block';
|
||||||
|
|
||||||
// Bind click
|
// Use mousedown instead of click to fire before blur event
|
||||||
customerSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
|
customerSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
|
||||||
if (item.dataset.login) {
|
if (item.dataset.login) {
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault(); // prevent input from losing focus before value is set
|
||||||
customerSearchInput.value = item.dataset.name;
|
customerSearchInput.value = item.dataset.name;
|
||||||
customerUserIdInput.value = item.dataset.login;
|
customerUserIdInput.value = item.dataset.login;
|
||||||
customerIdInput.value = item.dataset.customerId || '';
|
customerIdInput.value = item.dataset.customerId || '';
|
||||||
@@ -493,21 +499,14 @@ const TicketDetailView = {
|
|||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
customerSearchInput.addEventListener('focus', () => {
|
customerSearchInput.addEventListener('focus', () => {
|
||||||
customerSearchInput.value = '';
|
if (!customerUserIdInput.value) {
|
||||||
customerUserIdInput.value = '';
|
|
||||||
customerIdInput.value = '';
|
|
||||||
customerUserIdInput.dispatchEvent(new Event('change'));
|
|
||||||
customerIdInput.dispatchEvent(new Event('change'));
|
|
||||||
customerSearchInput.dispatchEvent(new Event('input'));
|
customerSearchInput.dispatchEvent(new Event('input'));
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close suggestions on click outside
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
if (customerSearchInput && e.target !== customerSearchInput && e.target !== customerSuggestionsDiv) {
|
|
||||||
customerSuggestionsDiv.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
customerSearchInput.addEventListener('blur', () => {
|
||||||
|
setTimeout(() => { customerSuggestionsDiv.style.display = 'none'; }, 150);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Reset quick-edit
|
// Reset quick-edit
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', () => {
|
||||||
|
|||||||
+174
-4
@@ -2,6 +2,9 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const pool = require('../db');
|
const pool = require('../db');
|
||||||
|
|
||||||
|
// In-memory cache for customer users fetched from LDAP
|
||||||
|
let ldapCustomerUsersCache = [];
|
||||||
|
|
||||||
// Helper for OTRS CE GenericInterface REST API calls
|
// Helper for OTRS CE GenericInterface REST API calls
|
||||||
async function otrsRequest(method, path, bodyData = {}) {
|
async function otrsRequest(method, path, bodyData = {}) {
|
||||||
const OTRS_API_USER = process.env.OTRS_API_USER;
|
const OTRS_API_USER = process.env.OTRS_API_USER;
|
||||||
@@ -19,7 +22,7 @@ async function otrsRequest(method, path, bodyData = {}) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 6000);
|
const timeoutId = setTimeout(() => controller.abort(), 15000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@@ -38,7 +41,7 @@ async function otrsRequest(method, path, bodyData = {}) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
if (err.name === 'AbortError') {
|
if (err.name === 'AbortError') {
|
||||||
throw new Error('OTRS REST API request timed out (6s limit exceeded)');
|
throw new Error('OTRS REST API request timed out (15s limit exceeded)');
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -199,11 +202,94 @@ router.get('/customer-companies/search', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/customer-users/search — Search customer users (always from local DB)
|
// GET /api/customer-users/search — Search customer users (supports LDAP via REST, fallbacks to DB)
|
||||||
router.get('/customer-users/search', async (req, res) => {
|
router.get('/customer-users/search', async (req, res) => {
|
||||||
try {
|
|
||||||
const { q = '', customer_company_id } = req.query;
|
const { q = '', customer_company_id } = req.query;
|
||||||
|
|
||||||
|
// 1. Try to search via OTRS GenericInterface REST API if configured
|
||||||
|
if (process.env.OTRS_API_URL && process.env.OTRS_API_USER) {
|
||||||
|
// Search the in-memory cache first if it contains data
|
||||||
|
if (ldapCustomerUsersCache.length > 0) {
|
||||||
|
const searchTerms = q.toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
|
let matchedUsers = ldapCustomerUsersCache;
|
||||||
|
|
||||||
|
if (searchTerms.length > 0) {
|
||||||
|
matchedUsers = matchedUsers.filter(u => {
|
||||||
|
const login = (u.login || '').toLowerCase();
|
||||||
|
const email = (u.email || '').toLowerCase();
|
||||||
|
const first = (u.first_name || '').toLowerCase();
|
||||||
|
const last = (u.last_name || '').toLowerCase();
|
||||||
|
const company = (u.customer_id || '').toLowerCase();
|
||||||
|
|
||||||
|
return searchTerms.every(term =>
|
||||||
|
login.includes(term) ||
|
||||||
|
email.includes(term) ||
|
||||||
|
first.includes(term) ||
|
||||||
|
last.includes(term) ||
|
||||||
|
company.includes(term)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customer_company_id) {
|
||||||
|
matchedUsers = matchedUsers.filter(u => u.customer_id === customer_company_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(matchedUsers.slice(0, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to live API search if cache is not yet populated
|
||||||
|
try {
|
||||||
|
const searchPattern = q ? `*${q}*` : '*';
|
||||||
|
const searchPayload = {
|
||||||
|
Search: searchPattern,
|
||||||
|
Valid: 1
|
||||||
|
};
|
||||||
|
if (customer_company_id) {
|
||||||
|
searchPayload.CustomerID = customer_company_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResult = await otrsRequest('POST', '/CustomerUserSearch', searchPayload);
|
||||||
|
|
||||||
|
const searchData = (searchResult && searchResult.Data) || searchResult;
|
||||||
|
if (searchData && searchData.CustomerUserID) {
|
||||||
|
let logins = searchData.CustomerUserID;
|
||||||
|
if (!Array.isArray(logins)) {
|
||||||
|
logins = [logins];
|
||||||
|
}
|
||||||
|
logins = logins.slice(0, 20); // limit to 20 results
|
||||||
|
|
||||||
|
// Fetch details for each login in parallel
|
||||||
|
const detailsPromises = logins.map(async (login) => {
|
||||||
|
try {
|
||||||
|
const getResult = await otrsRequest('POST', '/CustomerUserGet', { UserLogin: login });
|
||||||
|
const getData = (getResult && getResult.Data) || getResult;
|
||||||
|
if (getData && getData.CustomerUser) {
|
||||||
|
const u = getData.CustomerUser;
|
||||||
|
return {
|
||||||
|
login: u.UserLogin,
|
||||||
|
email: u.UserEmail || '',
|
||||||
|
first_name: u.UserFirstname || '',
|
||||||
|
last_name: u.UserLastname || '',
|
||||||
|
customer_id: u.UserCustomerID || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (getErr) {
|
||||||
|
console.warn(`Failed to fetch details for customer user ${login}:`, getErr.message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = (await Promise.all(detailsPromises)).filter(u => u !== null);
|
||||||
|
return res.json(users);
|
||||||
|
}
|
||||||
|
} catch (restErr) {
|
||||||
|
console.warn('Failed to search customer users via REST API, falling back to database query:', restErr.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fallback to local DB query
|
||||||
|
try {
|
||||||
let queryText;
|
let queryText;
|
||||||
let queryParams;
|
let queryParams;
|
||||||
if (q) {
|
if (q) {
|
||||||
@@ -310,4 +396,88 @@ router.get('/states/search', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper function to sync customer users from LDAP (OTRS API) into in-memory cache
|
||||||
|
async function syncCustomerUsers() {
|
||||||
|
if (!process.env.OTRS_API_URL || !process.env.OTRS_API_USER) {
|
||||||
|
console.log('[LDAP Sync] OTRS API not configured, skipping customer user sync.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[LDAP Sync] Starting customer user synchronization...');
|
||||||
|
try {
|
||||||
|
const searchResult = await otrsRequest('POST', '/CustomerUserSearch', { Search: '*', Valid: 1 });
|
||||||
|
const searchData = (searchResult && searchResult.Data) || searchResult;
|
||||||
|
if (!searchData || !searchData.CustomerUserID) {
|
||||||
|
console.log('[LDAP Sync] No customer users found to sync.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logins = searchData.CustomerUserID;
|
||||||
|
if (!Array.isArray(logins)) {
|
||||||
|
logins = [logins];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[LDAP Sync] Found ${logins.length} customer users. Fetching details...`);
|
||||||
|
|
||||||
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
const newCache = [];
|
||||||
|
// Fetch details in batches of 3 to avoid overloading the OTRS CGI server
|
||||||
|
const batchSize = 3;
|
||||||
|
for (let i = 0; i < logins.length; i += batchSize) {
|
||||||
|
const batchLogins = logins.slice(i, i + batchSize);
|
||||||
|
await Promise.all(batchLogins.map(async (login) => {
|
||||||
|
let retries = 2;
|
||||||
|
while (retries >= 0) {
|
||||||
|
try {
|
||||||
|
const getResult = await otrsRequest('POST', '/CustomerUserGet', { UserLogin: login });
|
||||||
|
const getData = (getResult && getResult.Data) || getResult;
|
||||||
|
if (getData && getData.CustomerUser) {
|
||||||
|
const u = getData.CustomerUser;
|
||||||
|
newCache.push({
|
||||||
|
login: u.UserLogin,
|
||||||
|
email: u.UserEmail || '',
|
||||||
|
first_name: u.UserFirstname || '',
|
||||||
|
last_name: u.UserLastname || '',
|
||||||
|
customer_id: u.UserCustomerID || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break; // Success, break retry loop
|
||||||
|
} catch (getErr) {
|
||||||
|
if (retries === 0) {
|
||||||
|
console.warn(`[LDAP Sync] Failed to sync customer user ${login} after retries:`, getErr.message);
|
||||||
|
} else {
|
||||||
|
await delay(200); // Wait 200ms before retrying
|
||||||
|
}
|
||||||
|
retries--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
// Add a small delay between batches
|
||||||
|
await delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
ldapCustomerUsersCache = newCache;
|
||||||
|
console.log(`[LDAP Sync] Completed. Synced ${ldapCustomerUsersCache.length} users to in-memory cache.`);
|
||||||
|
return ldapCustomerUsersCache.length;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[LDAP Sync] Error during customer user sync:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/customer-users/sync — Sync customer users from LDAP
|
||||||
|
router.post('/customer-users/sync', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const count = await syncCustomerUsers();
|
||||||
|
res.json({ message: `Sincronizzazione completata! ${count} utenti sincronizzati.`, count });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Errore durante la sincronizzazione', message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run LDAP synchronization in the background on startup (after 5 seconds)
|
||||||
|
setTimeout(() => {
|
||||||
|
syncCustomerUsers().catch(err => console.error('Startup LDAP sync failed:', err));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -459,6 +459,18 @@ router.post('/', async (req, res) => {
|
|||||||
const c = custRes.rows[0];
|
const c = custRes.rows[0];
|
||||||
customerFrom = `${c.first_name} ${c.last_name} <${c.email}>`;
|
customerFrom = `${c.first_name} ${c.last_name} <${c.email}>`;
|
||||||
senderTypeName = 'customer';
|
senderTypeName = 'customer';
|
||||||
|
} else if (process.env.OTRS_API_URL && process.env.OTRS_API_USER) {
|
||||||
|
try {
|
||||||
|
const getResult = await otrsRequest('POST', '/CustomerUserGet', { UserLogin: customer_user_id });
|
||||||
|
const getData = (getResult && getResult.Data) || getResult;
|
||||||
|
if (getData && getData.CustomerUser) {
|
||||||
|
const u = getData.CustomerUser;
|
||||||
|
customerFrom = `${u.UserFirstname || ''} ${u.UserLastname || ''} <${u.UserEmail || ''}>`.trim();
|
||||||
|
senderTypeName = 'customer';
|
||||||
|
}
|
||||||
|
} catch (apiErr) {
|
||||||
|
console.warn(`Failed to fetch LDAP customer details for ${customer_user_id}:`, apiErr.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user