TS175 linhas5.241 bytes
apps/worker/src/routes/premiacao.ts
SHA-256
eacccb865326ed5fac767e43f4f50ad74cdf6d45cb64648b515a090536ef3480
Somente leiturafonte-eb8a54281800
import { Hono } from 'hono'
import type { Env } from '../types'
import {
apurarPremiacao,
getSnapshotFixado,
setSnapshotFixado,
PremiacaoIndisponivel,
MIN_LINHAS_SNAPSHOT,
MESES_MIN_VITORIA,
REGIOES,
DIVULGAR_RESULTADO,
} from '../services/premiacao'
import type { Premiacao } from '../services/premiacao'
import { listSnapshots } from '../services/ranking'
const premiacao = new Hono<{ Bindings: Env }>()
function exigeAdmin(c: { req: { header: (k: string) => string | undefined }; env: Env }) {
const secret = c.req.header('X-Admin-Secret')
return Boolean(secret) && secret === c.env.ADMIN_SECRET
}
/**
* Versão pública enquanto o resultado não é divulgado: categorias e tamanho
* da coorte, sem vencedor, pódio nem snapshot que identifique a apuração.
*/
function semResultado(p: Premiacao) {
return {
edicao: p.edicao,
legislatura: p.legislatura,
divulgado: false,
coorte: p.coorte,
categorias: p.categorias.map((cat) => ({
id: cat.id,
eixo: cat.eixo,
titulo: cat.titulo,
descricao: cat.descricao,
criterio: cat.criterio,
peso: cat.peso,
regiao: cat.regiao,
uf: cat.uf,
ufs: cat.ufs,
elegiveis: cat.elegiveis,
})),
}
}
premiacao.get('/', async (c) => {
try {
const resultado = await apurarPremiacao(c.env)
if (!DIVULGAR_RESULTADO) {
// O admin vê a apuração completa; a resposta não pode ir para cache compartilhado.
if (exigeAdmin(c)) {
return c.json({ ...resultado, divulgado: false }, 200, {
'Cache-Control': 'private, no-store',
Vary: 'X-Admin-Secret',
})
}
return c.json(semResultado(resultado), 200, {
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=600',
Vary: 'X-Admin-Secret',
})
}
// Resultado congelado não muda até alguém repinar: pode cachear por mais tempo.
const maxAge = resultado.congelado ? 86400 : 3600
return c.json({ ...resultado, divulgado: true }, 200, {
'Cache-Control': `public, max-age=${maxAge}, stale-while-revalidate=600`,
})
} catch (err) {
if (err instanceof PremiacaoIndisponivel) {
// 503: a apuração recusa em vez de publicar prêmio sobre dado parcial.
return c.json(
{ error: 'premiacao indisponivel', motivo: err.motivo, detalhe: err.detalhe },
503,
)
}
console.error('[premiacao] erro:', err)
return c.json({ error: 'apuracao falhou', detalhe: String(err) }, 500)
}
})
/** Metadados estáveis: regiões, piso de sanidade e snapshot em uso. */
premiacao.get('/meta', async (c) => {
const fixado = await getSnapshotFixado(c.env)
return c.json(
{
edicao: 'leg57',
legislatura: 57,
divulgado: DIVULGAR_RESULTADO,
congelado: fixado !== null,
snapshot: fixado,
mesesMinimosVitoria: MESES_MIN_VITORIA,
minLinhasSnapshot: MIN_LINHAS_SNAPSHOT,
regioes: Object.entries(REGIOES).map(([id, r]) => ({
id,
nome: r.nome,
ufs: r.ufs,
})),
},
200,
{ 'Cache-Control': 'public, max-age=3600' },
)
})
/** Admin: lista snapshots disponíveis para congelar. */
premiacao.get('/snapshots', async (c) => {
if (!exigeAdmin(c)) return c.json({ error: 'unauthorized' }, 401)
const snapshots = await listSnapshots(c.env)
const fixado = await getSnapshotFixado(c.env)
return c.json({
fixado,
minLinhasSnapshot: MIN_LINHAS_SNAPSHOT,
snapshots: snapshots.map((s) => ({
...s,
apto: s.total >= MIN_LINHAS_SNAPSHOT,
emUso: s.computedAt === fixado,
})),
})
})
/**
* Admin: fixa o snapshot da apuração.
*
* Sem `computed_at`, congela o snapshot mais recente que passe no piso de
* sanidade. Recusa snapshots inexistentes ou truncados — é exatamente o caso
* que o piso existe para barrar.
*/
premiacao.post('/congelar', async (c) => {
if (!exigeAdmin(c)) return c.json({ error: 'unauthorized' }, 401)
const snapshots = await listSnapshots(c.env)
if (snapshots.length === 0) {
return c.json({ error: 'nenhum snapshot disponível' }, 503)
}
const pedido = c.req.query('computed_at')
const alvo = pedido
? snapshots.find((s) => s.computedAt === pedido)
: snapshots.find((s) => s.total >= MIN_LINHAS_SNAPSHOT)
if (!alvo) {
return c.json(
{
error: pedido
? `snapshot ${pedido} não existe`
: `nenhum snapshot com ao menos ${MIN_LINHAS_SNAPSHOT} linhas`,
disponiveis: snapshots.slice(0, 10),
},
404,
)
}
if (alvo.total < MIN_LINHAS_SNAPSHOT) {
return c.json(
{
error: 'snapshot incompleto',
detalhe: `${alvo.computedAt} tem ${alvo.total} linhas, mínimo ${MIN_LINHAS_SNAPSHOT}`,
},
422,
)
}
await setSnapshotFixado(c.env, alvo.computedAt)
return c.json({ ok: true, congelado: true, snapshot: alvo.computedAt, total: alvo.total })
})
/** Admin: volta a apurar sobre o snapshot mais recente. */
premiacao.post('/descongelar', async (c) => {
if (!exigeAdmin(c)) return c.json({ error: 'unauthorized' }, 401)
await setSnapshotFixado(c.env, null)
return c.json({ ok: true, congelado: false })
})
export { premiacao }