Files
otrs-turbo/routes/lookups.js
T

484 lines
15 KiB
JavaScript

const express = require('express');
const router = express.Router();
const pool = require('../db');
// In-memory cache for customer users fetched from LDAP
let ldapCustomerUsersCache = [];
// Helper for OTRS CE GenericInterface REST API calls
async function otrsRequest(method, path, bodyData = {}) {
const OTRS_API_USER = process.env.OTRS_API_USER;
const OTRS_API_PASSWORD = process.env.OTRS_API_PASSWORD;
const OTRS_API_URL = process.env.OTRS_API_URL;
if (!OTRS_API_URL || !OTRS_API_USER) {
return null;
}
const url = `${OTRS_API_URL.replace(/\/$/, '')}${path.startsWith('/') ? path : '/' + path}`;
const payload = {
UserLogin: OTRS_API_USER,
Password: OTRS_API_PASSWORD,
...bodyData
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`OTRS REST API error (${response.status}): ${errorText}`);
}
return await response.json();
} catch (err) {
clearTimeout(timeoutId);
if (err.name === 'AbortError') {
throw new Error('OTRS REST API request timed out (15s limit exceeded)');
}
throw err;
}
}
// GET /api/queues — Active queues
router.get('/queues', async (req, res) => {
try {
const result = await pool.query(
`SELECT q.id, q.name, q.comments
FROM queue q
WHERE q.valid_id = 1
ORDER BY q.name`
);
res.json(result.rows);
} catch (err) {
console.error('Error fetching queues:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/states — Ticket states with state type
router.get('/states', async (req, res) => {
try {
const result = await pool.query(
`SELECT ts.id, ts.name, tst.name AS type_name
FROM ticket_state ts
JOIN ticket_state_type tst ON ts.type_id = tst.id
WHERE ts.valid_id = 1
ORDER BY ts.id`
);
res.json(result.rows);
} catch (err) {
console.error('Error fetching states:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/priorities — Ticket priorities
router.get('/priorities', async (req, res) => {
try {
const result = await pool.query(
`SELECT id, name, color
FROM ticket_priority
WHERE valid_id = 1
ORDER BY id`
);
res.json(result.rows);
} catch (err) {
console.error('Error fetching priorities:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/users — Active agents/operators
router.get('/users', async (req, res) => {
try {
const result = await pool.query(
`SELECT id, login, first_name, last_name, title
FROM users
WHERE valid_id = 1
ORDER BY last_name, first_name`
);
res.json(result.rows);
} catch (err) {
console.error('Error fetching users:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/config — Application config
router.get('/config', (req, res) => {
res.json({
defaultAgentLogin: process.env.OTRS_API_USER || '',
dailyTargetTime: parseInt(process.env.DAILY_TARGET_TIME, 10) || 480
});
});
// GET /api/users/time-today — Get sum of today's time units for active agent
router.get('/users/time-today', async (req, res) => {
try {
const operatorId = parseInt(req.headers['x-agent-id'], 10) || 1;
const result = await pool.query(
`SELECT COALESCE(SUM(time_unit), 0) AS total_today
FROM time_accounting
WHERE create_by = $1 AND DATE(create_time) = CURRENT_DATE`,
[operatorId]
);
res.json({ totalToday: parseFloat(result.rows[0].total_today) });
} catch (err) {
console.error('Error fetching today\'s time units:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/types — Ticket types
router.get('/types', async (req, res) => {
try {
const result = await pool.query(
`SELECT id, name
FROM ticket_type
WHERE valid_id = 1
ORDER BY name`
);
res.json(result.rows);
} catch (err) {
console.error('Error fetching types:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/lock-types — Ticket lock types
router.get('/lock-types', async (req, res) => {
try {
const result = await pool.query(
`SELECT id, name FROM ticket_lock_type WHERE valid_id = 1 ORDER BY id`
);
res.json(result.rows);
} catch (err) {
console.error('Error fetching lock types:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/customer-companies/search — Search customer companies
router.get('/customer-companies/search', async (req, res) => {
try {
const { q = '' } = req.query;
let result;
if (!q) {
result = await pool.query(
`SELECT customer_id, name
FROM customer_company
WHERE valid_id = 1
ORDER BY name
LIMIT 20`
);
} else {
const searchTerm = `%${q}%`;
result = await pool.query(
`SELECT customer_id, name
FROM customer_company
WHERE valid_id = 1 AND (
customer_id ILIKE $1 OR
name ILIKE $1
)
ORDER BY name
LIMIT 20`,
[searchTerm]
);
}
res.json(result.rows);
} catch (err) {
console.error('Error searching customer companies:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/customer-users/search — Search customer users (supports LDAP via REST, fallbacks to DB)
router.get('/customer-users/search', async (req, res) => {
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 queryParams;
if (q) {
const searchTerm = `%${q}%`;
queryText = `
SELECT login, email, first_name, last_name, customer_id
FROM customer_user
WHERE valid_id = 1 AND (
login ILIKE $1 OR
email ILIKE $1 OR
first_name ILIKE $1 OR
last_name ILIKE $1
)
`;
queryParams = [searchTerm];
if (customer_company_id) {
queryText += ` AND customer_id = $2`;
queryParams.push(customer_company_id);
}
} else {
queryText = `
SELECT login, email, first_name, last_name, customer_id
FROM customer_user
WHERE valid_id = 1
`;
queryParams = [];
if (customer_company_id) {
queryText += ` AND customer_id = $1`;
queryParams.push(customer_company_id);
}
}
queryText += ` ORDER BY last_name, first_name LIMIT 20`;
const result = await pool.query(queryText, queryParams);
res.json(result.rows);
} catch (err) {
console.error('Error searching customer users:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/agents/search — Search active agents
router.get('/agents/search', async (req, res) => {
try {
const { q } = req.query;
const searchTerm = q ? `%${q}%` : '%';
const result = await pool.query(
`SELECT id, login, first_name, last_name
FROM users
WHERE valid_id = 1 AND (
login ILIKE $1 OR
first_name ILIKE $1 OR
last_name ILIKE $1
)
ORDER BY last_name, first_name
LIMIT 20`,
[searchTerm]
);
res.json(result.rows);
} catch (err) {
console.error('Error searching agents:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/queues/search — Search active queues
router.get('/queues/search', async (req, res) => {
try {
const { q } = req.query;
const searchTerm = q ? `%${q}%` : '%';
const result = await pool.query(
`SELECT id, name
FROM queue
WHERE valid_id = 1 AND name ILIKE $1
ORDER BY name
LIMIT 20`,
[searchTerm]
);
res.json(result.rows);
} catch (err) {
console.error('Error searching queues:', err);
res.status(500).json({ error: err.message });
}
});
// GET /api/states/search — Search active ticket states
router.get('/states/search', async (req, res) => {
try {
const { q } = req.query;
const searchTerm = q ? `%${q}%` : '%';
const result = await pool.query(
`SELECT id, name
FROM ticket_state
WHERE valid_id = 1 AND name ILIKE $1
ORDER BY name
LIMIT 20`,
[searchTerm]
);
res.json(result.rows);
} catch (err) {
console.error('Error searching states:', err);
res.status(500).json({ error: err.message });
}
});
// 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;