227 lines
9.2 KiB
JavaScript
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
|