266 lines
9.1 KiB
JavaScript
266 lines
9.1 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const pool = require('../db');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const { db } = require('../activityDb');
|
|
|
|
const ALGORITHM = 'aes-256-cbc';
|
|
const SECRET_KEY = crypto.createHash('sha256').update(process.env.CRYPTO_KEY || 'default_secret_key_12345').digest();
|
|
const IV_LENGTH = 16;
|
|
|
|
function encrypt(text) {
|
|
const iv = crypto.randomBytes(IV_LENGTH);
|
|
const cipher = crypto.createCipheriv(ALGORITHM, SECRET_KEY, iv);
|
|
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
return iv.toString('hex') + ':' + encrypted;
|
|
}
|
|
|
|
function decrypt(text) {
|
|
const textParts = text.split(':');
|
|
const iv = Buffer.from(textParts.shift(), 'hex');
|
|
const encryptedText = Buffer.from(textParts.join(':'), 'hex');
|
|
const decipher = crypto.createDecipheriv(ALGORITHM, SECRET_KEY, iv);
|
|
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
return decrypted;
|
|
}
|
|
|
|
// Ensure SQLite table exists for phrases
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS frasi_cache (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
tipo TEXT,
|
|
testo TEXT
|
|
)
|
|
`);
|
|
|
|
// Seed function to import plain-text files into SQLite cache
|
|
function seedPhrases() {
|
|
try {
|
|
const countRow = db.prepare("SELECT COUNT(*) AS count FROM frasi_cache").get();
|
|
if (countRow.count === 0) {
|
|
console.log('[Phrases Seed] SQLite frasi_cache is empty. Seeding...');
|
|
|
|
const seedFile = (fileName, type) => {
|
|
const txtPath = path.join(__dirname, `../public/${fileName}`);
|
|
if (fs.existsSync(txtPath)) {
|
|
const text = fs.readFileSync(txtPath, 'utf8');
|
|
const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
|
|
|
const insertStmt = db.prepare("INSERT INTO frasi_cache (tipo, testo) VALUES (?, ?)");
|
|
db.transaction(() => {
|
|
for (const line of lines) {
|
|
insertStmt.run(type, encrypt(line));
|
|
}
|
|
})();
|
|
console.log(`[Phrases Seed] Successfully seeded ${lines.length} encrypted ${type} phrases.`);
|
|
}
|
|
};
|
|
|
|
seedFile('demotivational.txt', 'demotivational');
|
|
seedFile('motivational.txt', 'motivational');
|
|
}
|
|
} catch (err) {
|
|
console.error('[Phrases Seed] Seeding failed:', err.message);
|
|
}
|
|
}
|
|
|
|
seedPhrases();
|
|
|
|
|
|
// GET /api/dashboard/stats — Dashboard statistics
|
|
router.get('/stats', async (req, res) => {
|
|
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
|
|
|
let previewLimit = 10;
|
|
try {
|
|
const row = db.prepare("SELECT preview_limit FROM agent_settings WHERE agent_id = ?").get(activeAgentId);
|
|
if (row) {
|
|
previewLimit = row.preview_limit;
|
|
}
|
|
} catch (err) {
|
|
console.error('Error reading agent_settings:', err.message);
|
|
}
|
|
|
|
try {
|
|
// All queries in parallel for speed
|
|
const [
|
|
byState,
|
|
byPriority,
|
|
byQueue,
|
|
todayCount,
|
|
weekCount,
|
|
totalOpen,
|
|
recentTickets,
|
|
escalated,
|
|
myOpenCount,
|
|
] = await Promise.all([
|
|
// Tickets by state (only open-ish states)
|
|
pool.query(
|
|
`SELECT ts.name AS state, tst.name AS state_type, COUNT(*) AS count
|
|
FROM ticket t
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')
|
|
GROUP BY ts.name, tst.name
|
|
ORDER BY count DESC`
|
|
),
|
|
// Tickets by priority (open only)
|
|
pool.query(
|
|
`SELECT tp.name AS priority, tp.color, COUNT(*) AS count
|
|
FROM ticket t
|
|
JOIN ticket_priority tp ON t.ticket_priority_id = tp.id
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')
|
|
GROUP BY tp.name, tp.color, tp.id
|
|
ORDER BY tp.id`
|
|
),
|
|
// Tickets by queue (open only, top 10)
|
|
pool.query(
|
|
`SELECT q.name AS queue, COUNT(*) AS count
|
|
FROM ticket t
|
|
JOIN queue q ON t.queue_id = q.id
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')
|
|
GROUP BY q.name
|
|
ORDER BY count DESC
|
|
LIMIT 10`
|
|
),
|
|
// Created today
|
|
pool.query(
|
|
`SELECT COUNT(*) AS count FROM ticket
|
|
WHERE create_time >= CURRENT_DATE`
|
|
),
|
|
// Created this week
|
|
pool.query(
|
|
`SELECT COUNT(*) AS count FROM ticket
|
|
WHERE create_time >= date_trunc('week', CURRENT_DATE)`
|
|
),
|
|
// Total open
|
|
pool.query(
|
|
`SELECT COUNT(*) AS count
|
|
FROM ticket t
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')`
|
|
),
|
|
// Custom most recent tickets based on agent settings
|
|
pool.query(
|
|
`SELECT t.id, t.tn, t.title, ts.name AS state_name,
|
|
tp.name AS priority_name, tp.color AS priority_color,
|
|
q.name AS queue_name, t.create_time
|
|
FROM ticket t
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_priority tp ON t.ticket_priority_id = tp.id
|
|
JOIN queue q ON t.queue_id = q.id
|
|
ORDER BY t.create_time DESC
|
|
LIMIT $1`,
|
|
[previewLimit]
|
|
),
|
|
// Escalated tickets
|
|
pool.query(
|
|
`SELECT COUNT(*) AS count FROM ticket
|
|
WHERE escalation_time > 0
|
|
AND escalation_time < EXTRACT(EPOCH FROM NOW())`
|
|
),
|
|
// My open tickets count
|
|
pool.query(
|
|
`SELECT COUNT(*) AS count
|
|
FROM ticket t
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')
|
|
AND t.user_id = $1`,
|
|
[activeAgentId]
|
|
),
|
|
]);
|
|
|
|
res.json({
|
|
by_state: byState.rows,
|
|
by_priority: byPriority.rows,
|
|
by_queue: byQueue.rows,
|
|
created_today: parseInt(todayCount.rows[0].count),
|
|
created_this_week: parseInt(weekCount.rows[0].count),
|
|
total_open: parseInt(totalOpen.rows[0].count),
|
|
recent_tickets: recentTickets.rows,
|
|
escalated: parseInt(escalated.rows[0].count),
|
|
total_my_open: parseInt(myOpenCount.rows[0].count),
|
|
preview_limit: previewLimit,
|
|
});
|
|
} catch (err) {
|
|
console.error('Error fetching dashboard stats:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/phrases — Get decrypted phrases from SQLite cache
|
|
router.get('/phrases', (req, res) => {
|
|
const { tipo = 'demotivational' } = req.query;
|
|
try {
|
|
const rows = db.prepare("SELECT testo FROM frasi_cache WHERE tipo = ?").all(tipo);
|
|
const decryptedPhrases = rows.map(row => {
|
|
try {
|
|
return decrypt(row.testo);
|
|
} catch (decErr) {
|
|
console.warn('[Decrypt Phrase] Failed to decrypt phrase:', decErr.message);
|
|
return null;
|
|
}
|
|
}).filter(Boolean);
|
|
|
|
res.json(decryptedPhrases);
|
|
} catch (err) {
|
|
console.error('Error fetching decrypted phrases:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/settings — Retrieve settings for the agent
|
|
router.get('/settings', (req, res) => {
|
|
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
|
try {
|
|
const row = db.prepare("SELECT preview_limit, tickets_per_page FROM agent_settings WHERE agent_id = ?").get(activeAgentId);
|
|
if (row) {
|
|
res.json(row);
|
|
} else {
|
|
res.json({ preview_limit: 10, tickets_per_page: 50 });
|
|
}
|
|
} catch (err) {
|
|
console.error('Error reading agent settings:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/dashboard/settings — Save settings for the agent
|
|
router.post('/settings', (req, res) => {
|
|
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
|
const { preview_limit, tickets_per_page } = req.body;
|
|
|
|
try {
|
|
let currentSettings = { preview_limit: 10, tickets_per_page: 50 };
|
|
const row = db.prepare("SELECT preview_limit, tickets_per_page FROM agent_settings WHERE agent_id = ?").get(activeAgentId);
|
|
if (row) {
|
|
currentSettings = row;
|
|
}
|
|
|
|
const newPreviewLimit = preview_limit !== undefined ? parseInt(preview_limit, 10) : currentSettings.preview_limit;
|
|
const newTicketsPerPage = tickets_per_page !== undefined ? parseInt(tickets_per_page, 10) : currentSettings.tickets_per_page;
|
|
|
|
db.prepare(`
|
|
INSERT OR REPLACE INTO agent_settings (agent_id, preview_limit, tickets_per_page)
|
|
VALUES (?, ?, ?)
|
|
`).run(activeAgentId, newPreviewLimit, newTicketsPerPage);
|
|
|
|
res.json({ success: true, preview_limit: newPreviewLimit, tickets_per_page: newTicketsPerPage });
|
|
} catch (err) {
|
|
console.error('Error saving agent settings:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|