Files

346 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict'
const { Router } = require('express')
const { query, queryOne } = require('../../database/postgres')
const { nwAuth } = require('./auth')
function cleanPhone(tel) {
return String(tel || '').replace(/\D/g, '')
}
function cleanCpf(cpf) {
return String(cpf || '').replace(/\D/g, '')
}
function createRoutes() {
const router = Router()
router.use(nwAuth)
// ── GET /nw/ping ──────────────────────────────────────────────────────────
router.get('/ping', (_req, res) => {
res.json({ ok: true, system: 'alemao-conveniencias', ts: new Date().toISOString() })
})
// ── GET /nw/cliente?telefone= ─────────────────────────────────────────────
// Retorna dados do cliente + tickets da rifa ativa + clube ativo + candidaturas
router.get('/cliente', async (req, res) => {
const { telefone } = req.query
if (!telefone) return res.status(400).json({ error: '"telefone" é obrigatório' })
const clean = cleanPhone(telefone)
try {
const user = await queryOne(
`SELECT id, nome, telefone, cpf, email, timestamp AS created_at
FROM users
WHERE REGEXP_REPLACE(telefone, '\\D', '', 'g') = $1`,
[clean]
)
if (!user) return res.json({ found: false })
const cpf = cleanCpf(user.cpf)
// Tickets da rifa ativa
const tickets = await query(
`SELECT t.numero, r.titulo AS sorteio, t.data_compra
FROM tickets t
JOIN raffles r ON r.id = t.raffle_id
WHERE REGEXP_REPLACE(t.usuario_telefone, '\\D', '', 'g') = $1
AND r.ativo = 1
ORDER BY t.numero ASC`,
[clean]
)
// Clube(s) ativo(s)
const clubes = cpf ? await query(
`SELECT cm.id, cm.status, cm.next_billing_at,
cp.name AS plano, cp.price_monthly,
bc.name AS clube, bc.slug AS clube_slug, bc.specialty
FROM club_members cm
JOIN club_plans cp ON cp.id = cm.plan_id
JOIN benefit_clubs bc ON bc.id = cm.club_id
WHERE cm.user_cpf = $1 AND cm.status IN ('active','pending_payment')
ORDER BY cm.created_at DESC`,
[cpf]
) : []
// Candidaturas ativas (últimas 3)
const candidaturas = cpf ? await query(
`SELECT ja.status, ja.applied_at,
j.title AS vaga, j.work_mode, j.job_type, j.location
FROM job_applications ja
JOIN jobs j ON j.id = ja.job_id
WHERE ja.applicant_cpf = $1
ORDER BY ja.applied_at DESC
LIMIT 3`,
[cpf]
) : []
res.json({
found: true,
cliente: {
...user,
tickets,
clubes,
candidaturas,
},
})
} catch (err) {
console.error('[nw/cliente]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/clube?cpf= ────────────────────────────────────────────────────
// Retorna plano ativo do cliente, clube, dependentes e parceiros próximos
router.get('/clube', async (req, res) => {
const { cpf } = req.query
if (!cpf) return res.status(400).json({ error: '"cpf" é obrigatório' })
const cleanedCpf = cleanCpf(cpf)
try {
const memberships = await query(
`SELECT cm.id, cm.status, cm.next_billing_at, cm.started_at,
cp.name AS plano, cp.price_monthly, cp.max_dependents, cp.features,
bc.id AS clube_id, bc.name AS clube, bc.slug AS clube_slug,
bc.specialty, bc.conditions, bc.rules
FROM club_members cm
JOIN club_plans cp ON cp.id = cm.plan_id
JOIN benefit_clubs bc ON bc.id = cm.club_id
WHERE cm.user_cpf = $1
ORDER BY cm.status = 'active' DESC, cm.created_at DESC`,
[cleanedCpf]
)
if (!memberships.length) return res.json({ encontrado: false, memberships: [] })
// Para cada associação ativa, busca dependentes e parceiros
const result = await Promise.all(memberships.map(async (m) => {
const [dependentes, parceiros] = await Promise.all([
query(
`SELECT nome, parentesco, cpf, nascimento
FROM club_dependents
WHERE member_id = $1
ORDER BY nome ASC`,
[m.id]
),
query(
`SELECT name, specialty, phone, address, discount_percent, notes
FROM club_partners
WHERE club_id = $1 AND active = true
ORDER BY name ASC
LIMIT 10`,
[m.clube_id]
),
])
return { ...m, dependentes, parceiros }
}))
res.json({ encontrado: true, memberships: result })
} catch (err) {
console.error('[nw/clube]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/candidaturas?cpf= ─────────────────────────────────────────────
// Retorna todas as candidaturas do candidato com status detalhado
router.get('/candidaturas', async (req, res) => {
const { cpf } = req.query
if (!cpf) return res.status(400).json({ error: '"cpf" é obrigatório' })
const cleanedCpf = cleanCpf(cpf)
try {
const candidaturas = await query(
`SELECT ja.id, ja.status, ja.applied_at, ja.cover_letter,
j.title AS vaga, j.work_mode, j.job_type, j.location, j.category
FROM job_applications ja
JOIN jobs j ON j.id = ja.job_id
WHERE ja.applicant_cpf = $1
ORDER BY ja.applied_at DESC`,
[cleanedCpf]
)
const statusLabels = {
novo: 'Candidatura recebida',
em_analise: 'Em análise pela empresa',
aprovado: 'Aprovado! Aguarde contato',
reprovado: 'Não selecionado desta vez',
contratado: 'Contratado — parabéns!',
}
const enriched = candidaturas.map(c => ({
...c,
status_label: statusLabels[c.status] || c.status,
}))
res.json({ total: enriched.length, candidaturas: enriched })
} catch (err) {
console.error('[nw/candidaturas]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/parceiros?clube=slug ──────────────────────────────────────────
// Lista parceiros de um clube (para indicar onde o cliente pode usar o benefício)
router.get('/parceiros', async (req, res) => {
const { clube } = req.query
if (!clube) return res.status(400).json({ error: '"clube" (slug) é obrigatório' })
try {
const clubeData = await queryOne(
`SELECT id, name, specialty FROM benefit_clubs WHERE slug = $1 AND active = true`,
[clube]
)
if (!clubeData) return res.json({ encontrado: false, parceiros: [] })
const parceiros = await query(
`SELECT name, specialty, phone, address, discount_percent, notes
FROM club_partners
WHERE club_id = $1 AND active = true
ORDER BY name ASC`,
[clubeData.id]
)
res.json({
encontrado: true,
clube: { name: clubeData.name, specialty: clubeData.specialty },
parceiros,
})
} catch (err) {
console.error('[nw/parceiros]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/vagas ─────────────────────────────────────────────────────────
// Lista vagas abertas (para responder perguntas sobre oportunidades de emprego)
router.get('/vagas', async (_req, res) => {
try {
const vagas = await query(
`SELECT title, category, work_mode, job_type, location,
salary_min, salary_max, salary_hide, slots,
published_at, expires_at
FROM jobs
WHERE active = true
AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY published_at DESC
LIMIT 20`
)
const workModeLabels = { presencial: 'Presencial', hibrido: 'Híbrido', remoto: 'Remoto' }
const jobTypeLabels = { clt: 'CLT', pj: 'PJ', freelance: 'Freelance', estagio: 'Estágio', temporario: 'Temporário' }
const enriched = vagas.map(v => ({
...v,
work_mode_label: workModeLabels[v.work_mode] || v.work_mode,
job_type_label: jobTypeLabels[v.job_type] || v.job_type,
salario: v.salary_hide || (!v.salary_min && !v.salary_max)
? 'A combinar'
: v.salary_min && v.salary_max
? `R$ ${Number(v.salary_min).toLocaleString('pt-BR', { maximumFractionDigits: 0 })} R$ ${Number(v.salary_max).toLocaleString('pt-BR', { maximumFractionDigits: 0 })}`
: v.salary_min
? `A partir de R$ ${Number(v.salary_min).toLocaleString('pt-BR', { maximumFractionDigits: 0 })}`
: `Até R$ ${Number(v.salary_max).toLocaleString('pt-BR', { maximumFractionDigits: 0 })}`,
}))
res.json({ total: enriched.length, vagas: enriched })
} catch (err) {
console.error('[nw/vagas]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/promocoes ─────────────────────────────────────────────────────
router.get('/promocoes', async (_req, res) => {
try {
const promos = await query(
`SELECT id, nome, descricao, preco_original, preco_promocional
FROM promotions
WHERE ativo = 1
ORDER BY id ASC`
)
res.json(promos)
} catch (err) {
console.error('[nw/promocoes]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/sorteio ───────────────────────────────────────────────────────
router.get('/sorteio', async (_req, res) => {
try {
const sorteio = await queryOne(
"SELECT * FROM raffles WHERE ativo = 1 ORDER BY id DESC LIMIT 1"
)
if (!sorteio) return res.json({ ativo: false })
const countRow = await queryOne(
`SELECT COUNT(*) AS total
FROM tickets
WHERE raffle_id = $1
AND (status = 'sold' OR (status = 'reserved' AND reserved_until > NOW()))`,
[sorteio.id]
)
const total_vendidos = parseInt(countRow?.total ?? 0, 10)
res.json({
ativo: true,
sorteio: {
id: sorteio.id,
titulo: sorteio.titulo,
descricao: sorteio.descricao,
preco_numero: sorteio.preco_numero,
total_numeros: sorteio.total_numeros,
numeros_vendidos: total_vendidos,
numeros_disponiveis: sorteio.total_numeros - total_vendidos,
},
})
} catch (err) {
console.error('[nw/sorteio]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
// ── GET /nw/sorteio/tickets?telefone= ────────────────────────────────────
router.get('/sorteio/tickets', async (req, res) => {
const { telefone } = req.query
if (!telefone) return res.status(400).json({ error: '"telefone" é obrigatório' })
const clean = cleanPhone(telefone)
try {
const sorteio = await queryOne(
"SELECT id, titulo FROM raffles WHERE ativo = 1 ORDER BY id DESC LIMIT 1"
)
if (!sorteio) return res.json({ ativo: false, tickets: [] })
const tickets = await query(
`SELECT t.numero, t.data_compra
FROM tickets t
WHERE t.raffle_id = $1
AND REGEXP_REPLACE(t.usuario_telefone, '\\D', '', 'g') = $2
AND (t.status = 'sold' OR (t.status = 'reserved' AND t.reserved_until > NOW()))
ORDER BY t.numero ASC`,
[sorteio.id, clean]
)
res.json({
sorteio: sorteio.titulo,
telefone,
total: tickets.length,
tickets,
})
} catch (err) {
console.error('[nw/sorteio/tickets]', err.message)
res.status(500).json({ error: 'Erro interno' })
}
})
return router
}
module.exports = { createRoutes }