Extract backend tenant routes
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
99
backend/routes/tenantsRoutes.js
Normal file
99
backend/routes/tenantsRoutes.js
Normal file
@@ -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: `
|
||||
<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();
|
||||
}
|
||||
});
|
||||
|
||||
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 };
|
||||
Reference in New Issue
Block a user