Extract remaining backend routes
This commit is contained in:
61
backend/routes/apiKeysRoutes.js
Normal file
61
backend/routes/apiKeysRoutes.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const { requireRole } = require('../middleware/auth');
|
||||
const { hashSecret, maskSecret } = require('../utils/security');
|
||||
|
||||
const createApiKeysRouter = ({ pool }) => {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/api-keys', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
try {
|
||||
const { tenantId } = req.query;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
if (!effectiveTenantId || effectiveTenantId === 'all') return res.json([]);
|
||||
|
||||
const [rows] = await pool.query(
|
||||
'SELECT id, name, created_at, last_used_at, CASE WHEN secret_key LIKE "masked:%" THEN CONCAT("fasto_sk_", RIGHT(secret_key, 6), "...") ELSE CONCAT(SUBSTRING(secret_key, 1, 14), "...") END as masked_key FROM api_keys WHERE tenant_id = ?',
|
||||
[effectiveTenantId]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/api-keys', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, tenantId } = req.body;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
try {
|
||||
const id = `apk_${crypto.randomUUID().split('-')[0]}`;
|
||||
const secretKey = `fasto_sk_${crypto.randomBytes(32).toString('hex')}`;
|
||||
|
||||
await pool.query(
|
||||
'INSERT INTO api_keys (id, tenant_id, name, secret_key, secret_hash) VALUES (?, ?, ?, ?, ?)',
|
||||
[id, effectiveTenantId, name || 'Nova Integração API', maskSecret(id, secretKey), hashSecret(secretKey)]
|
||||
);
|
||||
|
||||
res.status(201).json({ id, secret_key: secretKey, message: 'Chave criada. Salve-a agora, ela não será exibida novamente.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/api-keys/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
try {
|
||||
const [existing] = await pool.query('SELECT tenant_id FROM api_keys WHERE id = ?', [req.params.id]);
|
||||
if (existing.length === 0) return res.status(404).json({ error: 'Chave não encontrada' });
|
||||
if (req.user.role !== 'super_admin' && existing[0].tenant_id !== req.user.tenant_id) return res.status(403).json({ error: 'Acesso negado' });
|
||||
|
||||
await pool.query('DELETE FROM api_keys WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Chave de API revogada com sucesso.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createApiKeysRouter,
|
||||
};
|
||||
88
backend/routes/attendancesRoutes.js
Normal file
88
backend/routes/attendancesRoutes.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const express = require('express');
|
||||
const { canReadAttendance } = require('../policies/accessPolicy');
|
||||
|
||||
const parseAttendance = (attendance) => ({
|
||||
...attendance,
|
||||
attention_points: typeof attendance.attention_points === 'string' ? JSON.parse(attendance.attention_points) : attendance.attention_points,
|
||||
improvement_points: typeof attendance.improvement_points === 'string' ? JSON.parse(attendance.improvement_points) : attendance.improvement_points,
|
||||
converted: Boolean(attendance.converted),
|
||||
});
|
||||
|
||||
const createAttendancesRouter = ({ pool }) => {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/attendances', async (req, res) => {
|
||||
try {
|
||||
const { tenantId, userId, teamId, startDate, endDate, funnelStage, origin } = req.query;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
|
||||
let q = 'SELECT a.*, u.team_id FROM attendances a JOIN users u ON a.user_id = u.id WHERE a.tenant_id = ?';
|
||||
const params = [effectiveTenantId];
|
||||
|
||||
if (startDate && endDate) {
|
||||
q += ' AND a.created_at BETWEEN ? AND ?';
|
||||
params.push(new Date(startDate), new Date(endDate));
|
||||
}
|
||||
|
||||
if (req.user.role === 'agent') {
|
||||
q += ' AND a.user_id = ?';
|
||||
params.push(req.user.id);
|
||||
} else {
|
||||
if (req.user.role === 'manager') {
|
||||
q += ' AND u.team_id = ?';
|
||||
params.push(req.user.team_id);
|
||||
} else if (teamId && teamId !== 'all') {
|
||||
q += ' AND u.team_id = ?';
|
||||
params.push(teamId);
|
||||
}
|
||||
|
||||
if (userId && userId !== 'all') {
|
||||
if (userId.startsWith('u_') || userId.length === 36) {
|
||||
q += ' AND a.user_id = ?';
|
||||
params.push(userId);
|
||||
} else {
|
||||
q += ' AND u.slug = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (funnelStage && funnelStage !== 'all') {
|
||||
q += ' AND a.funnel_stage = ?';
|
||||
params.push(funnelStage);
|
||||
}
|
||||
if (origin && origin !== 'all') {
|
||||
q += ' AND a.origin = ?';
|
||||
params.push(origin);
|
||||
}
|
||||
|
||||
q += ' ORDER BY a.created_at DESC';
|
||||
const [rows] = await pool.query(q, params);
|
||||
res.json(rows.map(parseAttendance));
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/attendances/:id', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT a.*, u.team_id FROM attendances a JOIN users u ON a.user_id = u.id WHERE a.id = ?',
|
||||
[req.params.id]
|
||||
);
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Not found' });
|
||||
|
||||
if (!canReadAttendance(req.user, rows[0])) return res.status(403).json({ error: 'Acesso negado.' });
|
||||
|
||||
res.json(parseAttendance(rows[0]));
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createAttendancesRouter,
|
||||
};
|
||||
141
backend/routes/funnelsRoutes.js
Normal file
141
backend/routes/funnelsRoutes.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const { requireRole } = require('../middleware/auth');
|
||||
|
||||
const createFunnelsRouter = ({ pool }) => {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/funnels', async (req, res) => {
|
||||
try {
|
||||
const { tenantId } = req.query;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
if (!effectiveTenantId || effectiveTenantId === 'all') return res.json([]);
|
||||
|
||||
const [funnels] = await pool.query('SELECT * FROM funnels WHERE tenant_id = ? ORDER BY created_at ASC', [effectiveTenantId]);
|
||||
|
||||
if (funnels.length === 0) {
|
||||
const fid = `funnel_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query('INSERT INTO funnels (id, tenant_id, name) VALUES (?, ?, ?)', [fid, effectiveTenantId, 'Funil Padrão']);
|
||||
|
||||
const defaultStages = [
|
||||
{ name: 'Sem atendimento', color: 'bg-zinc-100 text-zinc-700 border-zinc-200 dark:bg-dark-input dark:text-dark-muted dark:border-dark-border', order: 0 },
|
||||
{ name: 'Identificação', color: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800', order: 1 },
|
||||
{ name: 'Negociação', color: 'bg-purple-100 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800', order: 2 },
|
||||
{ name: 'Ganhos', color: 'bg-green-100 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-400 dark:border-green-800', order: 3 },
|
||||
{ name: 'Perdidos', color: 'bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800', order: 4 }
|
||||
];
|
||||
|
||||
for (const s of defaultStages) {
|
||||
const sid = `stage_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query(
|
||||
'INSERT INTO funnel_stages (id, funnel_id, name, color_class, order_index) VALUES (?, ?, ?, ?, ?)',
|
||||
[sid, fid, s.name, s.color, s.order]
|
||||
);
|
||||
}
|
||||
|
||||
await pool.query('UPDATE teams SET funnel_id = ? WHERE tenant_id = ? AND funnel_id IS NULL', [fid, effectiveTenantId]);
|
||||
funnels.push({ id: fid, tenant_id: effectiveTenantId, name: 'Funil Padrão' });
|
||||
}
|
||||
|
||||
const [stages] = await pool.query('SELECT * FROM funnel_stages WHERE funnel_id IN (?) ORDER BY order_index ASC', [funnels.map(f => f.id)]);
|
||||
const [teams] = await pool.query('SELECT id, funnel_id FROM teams WHERE tenant_id = ? AND funnel_id IS NOT NULL', [effectiveTenantId]);
|
||||
|
||||
const result = funnels.map(f => ({
|
||||
...f,
|
||||
stages: stages.filter(s => s.funnel_id === f.id),
|
||||
teamIds: teams.filter(t => t.funnel_id === f.id).map(t => t.id)
|
||||
}));
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error("GET /funnels error:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/funnels', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, tenantId } = req.body;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
try {
|
||||
const fid = `funnel_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query('INSERT INTO funnels (id, tenant_id, name) VALUES (?, ?, ?)', [fid, effectiveTenantId, name]);
|
||||
res.status(201).json({ id: fid });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/funnels/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, teamIds } = req.body;
|
||||
try {
|
||||
if (name) {
|
||||
await pool.query('UPDATE funnels SET name = ? WHERE id = ?', [name, req.params.id]);
|
||||
}
|
||||
if (teamIds && Array.isArray(teamIds)) {
|
||||
await pool.query('UPDATE teams SET funnel_id = NULL WHERE funnel_id = ?', [req.params.id]);
|
||||
if (teamIds.length > 0) {
|
||||
await pool.query('UPDATE teams SET funnel_id = ? WHERE id IN (?)', [req.params.id, teamIds]);
|
||||
}
|
||||
}
|
||||
res.json({ message: 'Funnel updated.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/funnels/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM funnel_stages WHERE funnel_id = ?', [req.params.id]);
|
||||
await pool.query('UPDATE teams SET funnel_id = NULL WHERE funnel_id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM funnels WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Funnel deleted.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/funnels/:id/stages', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, color_class, order_index } = req.body;
|
||||
try {
|
||||
const sid = `stage_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query(
|
||||
'INSERT INTO funnel_stages (id, funnel_id, name, color_class, order_index) VALUES (?, ?, ?, ?, ?)',
|
||||
[sid, req.params.id, name, color_class, order_index || 0]
|
||||
);
|
||||
res.status(201).json({ id: sid });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/funnel_stages/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, color_class, order_index } = req.body;
|
||||
try {
|
||||
const [existing] = await pool.query('SELECT * FROM funnel_stages WHERE id = ?', [req.params.id]);
|
||||
if (existing.length === 0) return res.status(404).json({ error: 'Stage not found' });
|
||||
|
||||
await pool.query(
|
||||
'UPDATE funnel_stages SET name = ?, color_class = ?, order_index = ? WHERE id = ?',
|
||||
[name || existing[0].name, color_class || existing[0].color_class, order_index !== undefined ? order_index : existing[0].order_index, req.params.id]
|
||||
);
|
||||
res.json({ message: 'Stage updated.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/funnel_stages/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM funnel_stages WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Stage deleted.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createFunnelsRouter,
|
||||
};
|
||||
155
backend/routes/integrationsRoutes.js
Normal file
155
backend/routes/integrationsRoutes.js
Normal file
@@ -0,0 +1,155 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const { requireRole } = require('../middleware/auth');
|
||||
const { recordActivity } = require('../services/activityService');
|
||||
|
||||
const requireApiKey = (req, res) => {
|
||||
if (req.user.is_api_key) return true;
|
||||
res.status(403).json({ error: 'Endpoint restrito a chaves de API.' });
|
||||
return false;
|
||||
};
|
||||
|
||||
const createIntegrationsRouter = ({ pool }) => {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/integration/users', requireRole(['admin']), async (req, res) => {
|
||||
if (!requireApiKey(req, res)) return;
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT u.id, u.name, u.email, t.name as team_name FROM users u LEFT JOIN teams t ON u.team_id = t.id WHERE u.tenant_id = ? AND u.status = "active"',
|
||||
[req.user.tenant_id]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/integration/origins', requireRole(['admin']), async (req, res) => {
|
||||
if (!requireApiKey(req, res)) return;
|
||||
try {
|
||||
const [groups] = await pool.query('SELECT id, name FROM origin_groups WHERE tenant_id = ?', [req.user.tenant_id]);
|
||||
if (groups.length === 0) return res.json([]);
|
||||
|
||||
const [items] = await pool.query('SELECT origin_group_id, name FROM origin_items WHERE origin_group_id IN (?) ORDER BY created_at ASC', [groups.map(g => g.id)]);
|
||||
const [teams] = await pool.query('SELECT id as team_id, name as team_name, origin_group_id FROM teams WHERE tenant_id = ? AND origin_group_id IS NOT NULL', [req.user.tenant_id]);
|
||||
|
||||
const result = groups.map(g => ({
|
||||
group_name: g.name,
|
||||
origins: items.filter(i => i.origin_group_id === g.id).map(i => i.name),
|
||||
assigned_teams: teams.filter(t => t.origin_group_id === g.id).map(t => ({ id: t.team_id, name: t.team_name }))
|
||||
}));
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/integration/funnels', requireRole(['admin']), async (req, res) => {
|
||||
if (!requireApiKey(req, res)) return;
|
||||
try {
|
||||
const [funnels] = await pool.query('SELECT id, name FROM funnels WHERE tenant_id = ?', [req.user.tenant_id]);
|
||||
if (funnels.length === 0) return res.json([]);
|
||||
|
||||
const [stages] = await pool.query('SELECT funnel_id, name, order_index FROM funnel_stages WHERE funnel_id IN (?) ORDER BY order_index ASC', [funnels.map(f => f.id)]);
|
||||
const [teams] = await pool.query('SELECT id as team_id, name as team_name, funnel_id FROM teams WHERE tenant_id = ? AND funnel_id IS NOT NULL', [req.user.tenant_id]);
|
||||
|
||||
const result = funnels.map(f => ({
|
||||
funnel_name: f.name,
|
||||
stages: stages.filter(s => s.funnel_id === f.id).map(s => s.name),
|
||||
assigned_teams: teams.filter(t => t.funnel_id === f.id).map(t => ({ id: t.team_id, name: t.team_name }))
|
||||
}));
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/integration/attendances', requireRole(['admin']), async (req, res) => {
|
||||
if (!requireApiKey(req, res)) return;
|
||||
|
||||
const {
|
||||
user_id,
|
||||
origin,
|
||||
funnel_stage,
|
||||
title,
|
||||
full_summary,
|
||||
score,
|
||||
first_response_time_min,
|
||||
handling_time_min,
|
||||
product_requested,
|
||||
product_sold,
|
||||
converted,
|
||||
attention_points,
|
||||
improvement_points
|
||||
} = req.body;
|
||||
|
||||
if (!user_id || !origin || !funnel_stage || !title) {
|
||||
return res.status(400).json({ error: 'Campos obrigatórios ausentes: user_id, origin, funnel_stage, title' });
|
||||
}
|
||||
|
||||
try {
|
||||
const [users] = await pool.query('SELECT id FROM users WHERE id = ? AND tenant_id = ? AND status = "active"', [user_id, req.user.tenant_id]);
|
||||
if (users.length === 0) return res.status(400).json({ error: 'user_id inválido, inativo ou não pertence a esta organização.' });
|
||||
|
||||
const attId = `att_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query(
|
||||
`INSERT INTO attendances (
|
||||
id, tenant_id, user_id, title, full_summary, score,
|
||||
first_response_time_min, handling_time_min,
|
||||
funnel_stage, origin, product_requested, product_sold,
|
||||
converted, attention_points, improvement_points
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
attId,
|
||||
req.user.tenant_id,
|
||||
user_id,
|
||||
title,
|
||||
full_summary || null,
|
||||
score || 0,
|
||||
first_response_time_min || 0,
|
||||
handling_time_min || 0,
|
||||
funnel_stage,
|
||||
origin,
|
||||
product_requested || null,
|
||||
product_sold || null,
|
||||
converted ? 1 : 0,
|
||||
attention_points ? JSON.stringify(attention_points) : null,
|
||||
improvement_points ? JSON.stringify(improvement_points) : null
|
||||
]
|
||||
);
|
||||
|
||||
if (converted) {
|
||||
const [managers] = await pool.query(
|
||||
"SELECT id FROM users WHERE tenant_id = ? AND role IN ('admin', 'manager') AND id != ?",
|
||||
[req.user.tenant_id, user_id]
|
||||
);
|
||||
const [agentInfo] = await pool.query("SELECT name FROM users WHERE id = ?", [user_id]);
|
||||
const agentName = agentInfo[0]?.name || 'Um agente';
|
||||
|
||||
for (const m of managers) {
|
||||
await recordActivity(pool, {
|
||||
userId: m.id,
|
||||
type: 'success',
|
||||
title: 'Venda Fechada!',
|
||||
message: `${agentName} converteu um lead em ${funnel_stage}.`,
|
||||
link: `/attendances/${attId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.status(201).json({ id: attId, message: 'Atendimento registrado com sucesso.' });
|
||||
} catch (error) {
|
||||
console.error('Integration Error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createIntegrationsRouter,
|
||||
};
|
||||
138
backend/routes/originsRoutes.js
Normal file
138
backend/routes/originsRoutes.js
Normal file
@@ -0,0 +1,138 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const { requireRole } = require('../middleware/auth');
|
||||
|
||||
const createOriginsRouter = ({ pool }) => {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/origins', async (req, res) => {
|
||||
try {
|
||||
const { tenantId } = req.query;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
if (!effectiveTenantId || effectiveTenantId === 'all') return res.json([]);
|
||||
|
||||
const [groups] = await pool.query('SELECT * FROM origin_groups WHERE tenant_id = ? ORDER BY created_at ASC', [effectiveTenantId]);
|
||||
|
||||
if (groups.length === 0) {
|
||||
const gid = `origrp_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query('INSERT INTO origin_groups (id, tenant_id, name) VALUES (?, ?, ?)', [gid, effectiveTenantId, 'Origens Padrão']);
|
||||
|
||||
const defaultOrigins = [
|
||||
{ name: 'WhatsApp', color: 'bg-green-100 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-400 dark:border-green-800' },
|
||||
{ name: 'Instagram', color: 'bg-pink-100 text-pink-700 border-pink-200 dark:bg-pink-900/30 dark:text-pink-400 dark:border-pink-800' },
|
||||
{ name: 'Website', color: 'bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800' },
|
||||
{ name: 'LinkedIn', color: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800' },
|
||||
{ name: 'Indicação', color: 'bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-900/30 dark:text-orange-400 dark:border-orange-800' }
|
||||
];
|
||||
|
||||
for (const origin of defaultOrigins) {
|
||||
const oid = `oriitm_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query(
|
||||
'INSERT INTO origin_items (id, origin_group_id, name, color_class) VALUES (?, ?, ?, ?)',
|
||||
[oid, gid, origin.name, origin.color]
|
||||
);
|
||||
}
|
||||
|
||||
await pool.query('UPDATE teams SET origin_group_id = ? WHERE tenant_id = ? AND origin_group_id IS NULL', [gid, effectiveTenantId]);
|
||||
groups.push({ id: gid, tenant_id: effectiveTenantId, name: 'Origens Padrão' });
|
||||
}
|
||||
|
||||
const [items] = await pool.query('SELECT * FROM origin_items WHERE origin_group_id IN (?) ORDER BY created_at ASC', [groups.map(g => g.id)]);
|
||||
const [teams] = await pool.query('SELECT id, origin_group_id FROM teams WHERE tenant_id = ? AND origin_group_id IS NOT NULL', [effectiveTenantId]);
|
||||
|
||||
const result = groups.map(g => ({
|
||||
...g,
|
||||
items: items.filter(i => i.origin_group_id === g.id),
|
||||
teamIds: teams.filter(t => t.origin_group_id === g.id).map(t => t.id)
|
||||
}));
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error("GET /origins error:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/origins', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, tenantId } = req.body;
|
||||
const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id;
|
||||
try {
|
||||
const gid = `origrp_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query('INSERT INTO origin_groups (id, tenant_id, name) VALUES (?, ?, ?)', [gid, effectiveTenantId, name]);
|
||||
res.status(201).json({ id: gid });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/origins/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, teamIds } = req.body;
|
||||
try {
|
||||
if (name) {
|
||||
await pool.query('UPDATE origin_groups SET name = ? WHERE id = ?', [name, req.params.id]);
|
||||
}
|
||||
if (teamIds && Array.isArray(teamIds)) {
|
||||
await pool.query('UPDATE teams SET origin_group_id = NULL WHERE origin_group_id = ?', [req.params.id]);
|
||||
if (teamIds.length > 0) {
|
||||
await pool.query('UPDATE teams SET origin_group_id = ? WHERE id IN (?)', [req.params.id, teamIds]);
|
||||
}
|
||||
}
|
||||
res.json({ message: 'Origin group updated.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/origins/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM origin_items WHERE origin_group_id = ?', [req.params.id]);
|
||||
await pool.query('UPDATE teams SET origin_group_id = NULL WHERE origin_group_id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM origin_groups WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Origin group deleted.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/origins/:id/items', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, color_class } = req.body;
|
||||
try {
|
||||
const oid = `oriitm_${crypto.randomUUID().split('-')[0]}`;
|
||||
await pool.query(
|
||||
'INSERT INTO origin_items (id, origin_group_id, name, color_class) VALUES (?, ?, ?, ?)',
|
||||
[oid, req.params.id, name, color_class || 'bg-zinc-100 text-zinc-800 border-zinc-200']
|
||||
);
|
||||
res.status(201).json({ id: oid });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/origin_items/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
const { name, color_class } = req.body;
|
||||
try {
|
||||
const [existing] = await pool.query('SELECT * FROM origin_items WHERE id = ?', [req.params.id]);
|
||||
if (existing.length === 0) return res.status(404).json({ error: 'Origin item not found' });
|
||||
|
||||
await pool.query('UPDATE origin_items SET name = ?, color_class = ? WHERE id = ?', [name || existing[0].name, color_class || existing[0].color_class, req.params.id]);
|
||||
res.json({ message: 'Origin item updated.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/origin_items/:id', requireRole(['admin', 'super_admin']), async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM origin_items WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Origin item deleted.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createOriginsRouter,
|
||||
};
|
||||
80
backend/routes/searchRoutes.js
Normal file
80
backend/routes/searchRoutes.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const express = require('express');
|
||||
|
||||
const createSearchRouter = ({ pool }) => {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/search', async (req, res) => {
|
||||
const { q } = req.query;
|
||||
if (!q || q.length < 2) return res.json({ members: [], teams: [], attendances: [], organizations: [] });
|
||||
|
||||
const queryStr = `%${q}%`;
|
||||
const results = { members: [], teams: [], attendances: [], organizations: [] };
|
||||
|
||||
try {
|
||||
if (req.user.role !== 'agent') {
|
||||
let membersQ = 'SELECT id, name, email, slug, role, team_id, avatar_url FROM users WHERE (name LIKE ? OR email LIKE ?)';
|
||||
const membersParams = [queryStr, queryStr];
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
membersQ += ' AND tenant_id = ?';
|
||||
membersParams.push(req.user.tenant_id);
|
||||
} else if (req.user.role === 'manager') {
|
||||
membersQ += ' AND tenant_id = ? AND (team_id = ? OR id = ?)';
|
||||
membersParams.push(req.user.tenant_id, req.user.team_id, req.user.id);
|
||||
}
|
||||
|
||||
const [members] = await pool.query(membersQ, membersParams);
|
||||
results.members = members;
|
||||
}
|
||||
|
||||
if (req.user.role !== 'agent') {
|
||||
let teamsQ = 'SELECT id, name, description FROM teams WHERE name LIKE ?';
|
||||
const teamsParams = [queryStr];
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
teamsQ += ' AND tenant_id = ?';
|
||||
teamsParams.push(req.user.tenant_id);
|
||||
} else if (req.user.role === 'manager') {
|
||||
teamsQ += ' AND tenant_id = ? AND id = ?';
|
||||
teamsParams.push(req.user.tenant_id, req.user.team_id);
|
||||
}
|
||||
|
||||
const [teams] = await pool.query(teamsQ, teamsParams);
|
||||
results.teams = teams;
|
||||
}
|
||||
|
||||
if (req.user.role === 'super_admin') {
|
||||
const [orgs] = await pool.query('SELECT id, name, slug, status FROM tenants WHERE name LIKE ? OR slug LIKE ? LIMIT 5', [queryStr, queryStr]);
|
||||
results.organizations = orgs;
|
||||
}
|
||||
|
||||
let attendancesQ = 'SELECT a.id, a.title, a.created_at, u.name as user_name FROM attendances a JOIN users u ON a.user_id = u.id WHERE a.title LIKE ?';
|
||||
const attendancesParams = [queryStr];
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
attendancesQ += ' AND a.tenant_id = ?';
|
||||
attendancesParams.push(req.user.tenant_id);
|
||||
} else if (req.user.role === 'manager') {
|
||||
attendancesQ += ' AND a.tenant_id = ? AND u.team_id = ?';
|
||||
attendancesParams.push(req.user.tenant_id, req.user.team_id);
|
||||
} else if (req.user.role !== 'super_admin') {
|
||||
attendancesQ += ' AND a.user_id = ?';
|
||||
attendancesParams.push(req.user.id);
|
||||
}
|
||||
|
||||
attendancesQ += ' LIMIT 10';
|
||||
const [attendances] = await pool.query(attendancesQ, attendancesParams);
|
||||
results.attendances = attendances;
|
||||
|
||||
res.json(results);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createSearchRouter,
|
||||
};
|
||||
Reference in New Issue
Block a user