Files
mercado.clube67.com/backend/controllers/adminController.js
T

227 lines
9.2 KiB
JavaScript

'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