'use strict' const bcrypt = require('bcryptjs') const { queryOne, query, execute } = require('../database/postgres') const { signSuperToken } = require('../middleware/superAuth') // ── Auth ────────────────────────────────────────────────────────────────────── exports.login = async (req, res) => { try { const { email, password } = req.body if (!email || !password) return res.status(400).json({ error: 'Email e senha obrigatórios' }) const admin = await queryOne('SELECT * FROM super_admins WHERE email=$1', [email.toLowerCase()]) if (!admin) return res.status(401).json({ error: 'Credenciais inválidas' }) const ok = await bcrypt.compare(password, admin.password_hash) if (!ok) return res.status(401).json({ error: 'Credenciais inválidas' }) const token = signSuperToken(admin) res.cookie('super_token', token, { httpOnly: true, secure: process.env.COOKIE_SECURE === 'true', sameSite: 'lax', maxAge: 8 * 60 * 60 * 1000, }) res.json({ ok: true, name: admin.name, email: admin.email }) } catch (err) { console.error('[superAdmin] login:', err.message) res.status(500).json({ error: 'Erro interno' }) } } exports.logout = (req, res) => { res.clearCookie('super_token') res.json({ ok: true }) } exports.me = (req, res) => { res.json({ id: req.superAdmin.id, name: req.superAdmin.name, email: req.superAdmin.email }) } // ── Tenants ─────────────────────────────────────────────────────────────────── exports.listTenants = async (req, res) => { try { const tenants = await query(` SELECT t.*, tc.primary_color, tc.secondary_color, tc.logo_url, tc.contact_email, tc.contact_phone, tc.app_url FROM tenants t LEFT JOIN tenant_config tc ON tc.tenant_id = t.id ORDER BY t.id `) res.json(tenants ?? []) } catch (err) { console.error('[superAdmin] listTenants:', err.message) res.status(500).json({ error: 'Erro interno' }) } } exports.createTenant = async (req, res) => { try { const { slug, name, domain, primary_color, secondary_color, logo_url, contact_email, contact_phone, app_url } = req.body if (!slug || !name) return res.status(400).json({ error: 'slug e name obrigatórios' }) const result = await execute( `INSERT INTO tenants (slug, name, domain, active) VALUES ($1,$2,$3,TRUE) RETURNING id`, [slug.toLowerCase(), name, domain || null] ) const tenantId = result.rows[0].id await execute( `INSERT INTO tenant_config (tenant_id, primary_color, secondary_color, logo_url, contact_email, contact_phone, app_url) VALUES ($1,$2,$3,$4,$5,$6,$7)`, [tenantId, primary_color || '#f59e0b', secondary_color || '#10b981', logo_url || null, contact_email || null, contact_phone || null, app_url || null] ) res.status(201).json({ id: tenantId }) } catch (err) { if (err.code === '23505') return res.status(409).json({ error: 'Slug já existe' }) console.error('[superAdmin] createTenant:', err.message) res.status(500).json({ error: 'Erro interno' }) } } exports.updateTenant = async (req, res) => { try { const { id } = req.params const { name, domain, active, primary_color, secondary_color, logo_url, contact_email, contact_phone, app_url } = req.body await execute( `UPDATE tenants SET name=$1, domain=$2, active=$3 WHERE id=$4`, [name, domain || null, active !== false, id] ) await execute( `UPDATE tenant_config SET primary_color=$1, secondary_color=$2, logo_url=$3, contact_email=$4, contact_phone=$5, app_url=$6 WHERE tenant_id=$7`, [primary_color, secondary_color, logo_url || null, contact_email || null, contact_phone || null, app_url || null, id] ) res.json({ ok: true }) } catch (err) { console.error('[superAdmin] updateTenant:', err.message) res.status(500).json({ error: 'Erro interno' }) } } exports.deleteTenant = async (req, res) => { try { await execute('UPDATE tenants SET active=FALSE WHERE id=$1', [req.params.id]) res.json({ ok: true }) } catch (err) { res.status(500).json({ error: 'Erro interno' }) } } exports.getTenantStats = async (req, res) => { try { const { id } = req.params const tid = parseInt(id) const [users, orders, tickets, clubMembers] = await Promise.all([ queryOne(`SELECT COUNT(*) AS cnt FROM users WHERE tenant_id=$1`, [tid]), queryOne(`SELECT COUNT(*) AS cnt, COALESCE(SUM(total_amount),0) AS revenue FROM orders WHERE tenant_id=$1 AND status='paid'`, [tid]), queryOne(`SELECT COUNT(*) AS cnt FROM tickets WHERE tenant_id=$1 AND status='sold'`, [tid]), queryOne(`SELECT COUNT(*) AS cnt FROM club_members WHERE tenant_id=$1 AND status='active'`, [tid]), ]) res.json({ users: Number(users?.cnt ?? 0), orders: Number(orders?.cnt ?? 0), revenue: Number(orders?.revenue ?? 0), tickets_sold: Number(tickets?.cnt ?? 0), club_members: Number(clubMembers?.cnt ?? 0), }) } catch (err) { console.error('[superAdmin] getTenantStats:', err.message) res.status(500).json({ error: 'Erro interno' }) } } // ── Tenant Admins ───────────────────────────────────────────────────────────── exports.createTenantAdmin = async (req, res) => { try { const { id: tenantId } = req.params const { username, password, name } = req.body if (!username || !password) return res.status(400).json({ error: 'username e password obrigatórios' }) const hash = await bcrypt.hash(password, 10) const result = await execute( `INSERT INTO admins (username, password_hash, name, tenant_id) VALUES ($1,$2,$3,$4) RETURNING id`, [username, hash, name || username, tenantId] ) res.status(201).json({ id: result.rows[0].id }) } catch (err) { if (err.code === '23505') return res.status(409).json({ error: 'Username já existe' }) console.error('[superAdmin] createTenantAdmin:', err.message) res.status(500).json({ error: 'Erro interno' }) } }