diff --git a/backend/index.js b/backend/index.js
index 9b84e01..d4d7777 100644
--- a/backend/index.js
+++ b/backend/index.js
@@ -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: `
-
-
Sua organização foi criada
-
Você foi definido como administrador da organização ${name}.
-
Por favor, clique no botão abaixo para definir sua senha e concluir seu cadastro.
-
-
Este link expira em 15 minutos.
-
- `
- }).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);
diff --git a/backend/routes/tenantsRoutes.js b/backend/routes/tenantsRoutes.js
new file mode 100644
index 0000000..edb61e1
--- /dev/null
+++ b/backend/routes/tenantsRoutes.js
@@ -0,0 +1,99 @@
+const express = require('express');
+const crypto = require('crypto');
+const { requireRole } = require('../middleware/auth');
+
+const createTenantsRouter = ({ pool, transporter, getBaseUrl }) => {
+ const router = express.Router();
+
+ router.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 });
+ }
+ });
+
+ router.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']);
+
+ 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');
+ 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}`;
+
+ 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: `
+
+
Sua organização foi criada
+
Você foi definido como administrador da organização ${name}.
+
Por favor, clique no botão abaixo para definir sua senha e concluir seu cadastro.
+
+
Este link expira em 15 minutos.
+
+ `
+ }).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();
+ }
+ });
+
+ router.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 });
+ }
+ });
+
+ router.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 });
+ }
+ });
+
+ return router;
+};
+
+module.exports = { createTenantsRouter };