'use strict' const Admin = require('../models/Admin') const bcrypt = require('bcryptjs') const jwt = require('jsonwebtoken') const path = require('path') const { JWT_SECRET } = require('../middleware/auth') const Claim = require('../models/Claim') const NotificationService = require('../services/notificationService') const { query, queryOne } = require('../database/postgres') const adminController = { getLoginPage(req, res) { res.sendFile(path.join(__dirname, '..', 'views', 'admin', 'login.html')) }, getDashboardPage(req, res) { res.sendFile(path.join(__dirname, '..', 'views', 'admin', 'dashboard.html')) }, async login(req, res) { try { const { username, password } = req.body const cleanUser = username?.trim(); const admin = await Admin.findByUsername(cleanUser) if (!admin || !bcrypt.compareSync(password, admin.password_hash)) { return res.status(401).json({ error: 'Credenciais inválidas' }) } const token = jwt.sign( { id: admin.id, username: admin.username, tenant_id: admin.tenant_id || 1 }, JWT_SECRET, { expiresIn: '24h' } ) // Remove sessão de equipe antes de criar sessão de admin res.clearCookie('team_token') res.cookie('cloud_token', token, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000, secure: process.env.COOKIE_SECURE === 'true', sameSite: 'lax', }) return res.json({ success: true, redirect: '/admin/dashboard' }) } catch (error) { return res.status(500).json({ error: 'Erro interno no login' }) } }, logout(req, res) { res.clearCookie('cloud_token') res.clearCookie('team_token') res.redirect('/admin') }, async listAdmins(req, res) { try { const admins = await Admin.findAll() return res.json(admins) } catch (error) { return res.status(500).json({ error: 'Erro ao listar administradores' }) } }, async createAdmin(req, res) { try { const { username, password } = req.body if (!username || !password) { return res.status(400).json({ error: 'Usuário e senha são obrigatórios' }) } if (username.length < 3) return res.status(400).json({ error: 'Usuário curto' }) if (password.length < 8) return res.status(400).json({ error: 'Senha deve ter no mínimo 8 caracteres' }) const existing = await Admin.findByUsername(username) if (existing) return res.status(400).json({ error: 'Usuário existente' }) const hash = bcrypt.hashSync(password, 10) const result = await Admin.create(username, hash) return res.json({ success: true, id: result.lastInsertRowid, message: 'Sucesso' }) } catch (error) { return res.status(500).json({ error: 'Erro ao criar' }) } }, async updateAdmin(req, res) { try { const { id } = req.params const { password, username, nome, email, telefone } = req.body const admin = await Admin.findById(parseInt(id)) if (!admin) return res.status(404).json({ error: 'Não encontrado' }) const updateData = {} if (username && username !== admin.username) { const existing = await Admin.findByUsername(username) if (existing && existing.id !== parseInt(id)) return res.status(400).json({ error: 'Nome em uso' }) updateData.username = username } if (password) { if (password.length < 8) return res.status(400).json({ error: 'Senha deve ter no mínimo 8 caracteres' }) updateData.password_hash = bcrypt.hashSync(password, 10) } updateData.nome = nome || null updateData.email = email || null updateData.telefone = telefone || null await Admin.update(parseInt(id), updateData) return res.json({ success: true, message: 'Atualizado' }) } catch (error) { return res.status(500).json({ error: 'Erro ao atualizar' }) } }, async deleteAdmin(req, res) { try { const { id } = req.params if (parseInt(id) === req.admin.id) return res.status(400).json({ error: 'Não pode se excluir' }) const count = await Admin.count() if (count <= 1) return res.status(400).json({ error: 'Último admin' }) await Admin.delete(parseInt(id)) return res.json({ success: true, message: 'Removido' }) } catch (error) { return res.status(500).json({ error: 'Erro ao deletar' }) } }, // ── Histórico de Pedidos ────────────────────────────────────────────────── async listOrders(req, res) { try { const { status, from, to } = req.query const params = [] const where = [] if (req.tenant?.id) { where.push(`o.tenant_id = $${params.length + 1}`); params.push(req.tenant.id) } if (status) { where.push(`o.status = $${params.length + 1}`); params.push(status) } if (from) { where.push(`o.created_at >= $${params.length + 1}`); params.push(from) } if (to) { where.push(`o.created_at <= $${params.length + 1}`); params.push(to + 'T23:59:59') } const sql = ` SELECT o.id, o.status, o.total_amount, o.payment_method, o.created_at, o.updated_at, o.user_cpf, u.nome AS user_nome, u.telefone AS user_telefone, COUNT(t.id)::int AS ticket_count FROM orders o LEFT JOIN users u ON u.cpf = o.user_cpf LEFT JOIN tickets t ON t.order_id = o.id ${where.length ? 'WHERE ' + where.join(' AND ') : ''} GROUP BY o.id, u.nome, u.telefone ORDER BY o.created_at DESC LIMIT 300 ` const rows = await query(sql, params) return res.json(rows ?? []) } catch (err) { console.error('[Admin] listOrders:', err.message) return res.status(500).json({ error: 'Erro ao listar pedidos' }) } }, // ── Gestão de Resgates (raffle_claims) ──────────────────────────────────── async listClaims(req, res) { try { const { status } = req.query const params = [] const where = [] if (req.tenant?.id) { where.push(`r.tenant_id = $${params.length + 1}`); params.push(req.tenant.id) } if (status) { where.push(`c.status = $${params.length + 1}`); params.push(status) } let sql = ` SELECT c.id, c.status, c.delivery_address, c.delivery_notes, c.claimed_at, c.updated_at, r.titulo AS raffle_titulo, t.numero AS ticket_numero, u.nome AS user_nome, u.cpf AS user_cpf, u.telefone AS user_telefone FROM raffle_claims c JOIN raffles r ON r.id = c.raffle_id JOIN tickets t ON t.id = c.ticket_id LEFT JOIN users u ON u.cpf = c.user_cpf ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ` const rows = await query(sql, params) return res.json(rows ?? []) } catch (err) { return res.status(500).json({ error: 'Erro ao listar resgates' }) } }, async updateClaimStatus(req, res) { try { const { id } = req.params const { status } = req.body const VALID = ['pending', 'approved', 'delivered'] if (!VALID.includes(status)) { return res.status(400).json({ error: `Status inválido. Use: ${VALID.join(', ')}` }) } const claim = await Claim.getClaimDetails(id) if (!claim) return res.status(404).json({ error: 'Resgate não encontrado' }) await Claim.updateStatus(id, status) // Notifica o ganhador sobre a mudança de status const user = await queryOne('SELECT nome, telefone FROM users WHERE cpf = $1', [claim.user_cpf]) if (status === 'approved') { NotificationService.notifyClaimApproved( user?.telefone, claim.user_cpf, user?.nome, claim.raffle_titulo ).catch(() => {}) } else if (status === 'delivered') { NotificationService.notifyClaimDelivered( user?.telefone, claim.user_cpf, user?.nome, claim.raffle_titulo ).catch(() => {}) } return res.json({ ok: true, status }) } catch (err) { return res.status(500).json({ error: 'Erro ao atualizar resgate' }) } }, } module.exports = adminController