Extract backend tenant routes

This commit is contained in:
Cauê Faleiros
2026-05-29 11:00:16 -03:00
parent 34c64bda9a
commit 0f322e9c85
2 changed files with 102 additions and 83 deletions

View File

@@ -14,6 +14,7 @@ const { authenticateToken, requireRole } = require('./middleware/auth');
const { createAuthRouter } = require('./routes/authRoutes');
const { createUsersRouter } = require('./routes/usersRoutes');
const { createTeamsRouter } = require('./routes/teamsRoutes');
const { createTenantsRouter } = require('./routes/tenantsRoutes');
const { canReadAttendance } = require('./policies/accessPolicy');
const app = express();
@@ -75,6 +76,8 @@ apiRouter.use(createUsersRouter({ pool, upload, transporter, getBaseUrl }));
apiRouter.use(createTeamsRouter({ pool }));
apiRouter.use(createTenantsRouter({ pool, transporter, getBaseUrl }));
// --- Notifications Routes ---
apiRouter.get('/notifications', async (req, res) => {
try {
@@ -730,89 +733,6 @@ apiRouter.post('/integration/attendances', requireRole(['admin']), async (req, r
}
});
// --- Tenant Routes ---
apiRouter.get('/tenants', requireRole(['super_admin']), async (req, res) => {
try {
const q = 'SELECT t.*, (SELECT COUNT(*) FROM users u WHERE u.tenant_id = t.id) as user_count, (SELECT COUNT(*) FROM attendances a WHERE a.tenant_id = t.id) as attendance_count FROM tenants t';
const [rows] = await pool.query(q);
res.json(rows);
} catch (error) { res.status(500).json({ error: error.message }); }
});
apiRouter.post('/tenants', requireRole(['super_admin']), async (req, res) => {
const { name, slug, admin_email, status } = req.body;
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const tid = `tenant_${crypto.randomUUID().split('-')[0]}`;
await connection.query('INSERT INTO tenants (id, name, slug, admin_email, status) VALUES (?, ?, ?, ?, ?)', [tid, name, slug, admin_email, status || 'active']);
// Check if user already exists
const [existingUser] = await connection.query('SELECT id FROM users WHERE email = ?', [admin_email]);
if (existingUser.length === 0) {
const uid = `u_${crypto.randomUUID().split('-')[0]}`;
const userSlug = `admin-${crypto.randomBytes(4).toString('hex')}`;
const placeholderHash = 'pending_setup';
await connection.query('INSERT INTO users (id, tenant_id, name, email, password_hash, slug, role) VALUES (?, ?, ?, ?, ?, ?, ?)', [uid, tid, 'Admin', admin_email, placeholderHash, userSlug, 'admin']);
const token = crypto.randomBytes(32).toString('hex');
// Adicionar aos pending_registrations em vez de criar um usuário direto se quisermos que eles completem o cadastro.
// Ou, se quisermos apenas definir a senha, mantemos o password_resets.
// O usuário quer "like a register", então vamos enviar para uma página onde eles definem a senha.
await connection.query('INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 15 MINUTE))', [admin_email, token]);
const setupLink = `${getBaseUrl(req)}/#/setup-account?token=${token}`;
// Add Notification for Super Admins
const [superAdmins] = await connection.query("SELECT id FROM users WHERE role = 'super_admin'");
for (const sa of superAdmins) {
await connection.query(
'INSERT INTO notifications (id, user_id, type, title, message, link) VALUES (?, ?, ?, ?, ?, ?)',
[crypto.randomUUID(), sa.id, 'success', 'Nova Organização', `A organização ${name} foi criada.`, '/super-admin']
);
}
await transporter.sendMail({
from: `"Fasto" <${process.env.MAIL_FROM || 'nao-responda@blyzer.com.br'}>`,
to: admin_email,
subject: 'Bem-vindo ao Fasto - Conclua seu cadastro de Admin',
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 12px; background: #ffffff; color: #0f172a;">
<h2 style="color: #0f172a;">Sua organização foi criada</h2>
<p style="color: #475569;">Você foi definido como administrador da organização <strong>${name}</strong>.</p>
<p style="color: #475569;">Por favor, clique no botão abaixo para definir sua senha e concluir seu cadastro.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${setupLink}" style="background-color: #0f172a; color: white; padding: 12px 24px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block;">Finalizar Cadastro</a>
</div>
<p style="font-size: 12px; color: #94a3b8;">Este link expira em 15 minutos.</p>
</div>
`
}).catch(err => console.error("Email failed:", err));
}
await connection.commit();
res.status(201).json({ id: tid, message: 'Organização criada e convite enviado por e-mail.' });
} catch (error) { await connection.rollback(); res.status(500).json({ error: error.message }); } finally { connection.release(); }
});
apiRouter.put('/tenants/:id', requireRole(['super_admin']), async (req, res) => {
const { name, slug, admin_email, status } = req.body;
try {
await pool.query(
'UPDATE tenants SET name = ?, slug = ?, admin_email = ?, status = ? WHERE id = ?',
[name, slug || null, admin_email, status, req.params.id]
);
res.json({ message: 'Tenant updated successfully.' });
} catch (error) { res.status(500).json({ error: error.message }); }
});
apiRouter.delete('/tenants/:id', requireRole(['super_admin']), async (req, res) => {
try {
await pool.query('DELETE FROM tenants WHERE id = ?', [req.params.id]);
res.json({ message: 'Tenant deleted successfully.' });
} catch (error) { res.status(500).json({ error: error.message }); }
});
// Mount the API Router
app.use('/api', apiRouter);