170 lines
6.4 KiB
JavaScript
170 lines
6.4 KiB
JavaScript
'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' })
|
|
}
|
|
}
|