Iniciativa Cidadã Independente · Transparência Parlamentar
TS134 linhas3.988 bytes

apps/worker/src/routes/premiacao.ts

SHA-256

b8d5f7dd5272e7d2f8adba61363f65aee88c73b8f8dbd7c57f809a3696681457

Somente leiturafonte-e58fe007b6ee
import { Hono } from 'hono'
import type { Env } from '../types'
import {
  apurarPremiacao,
  getSnapshotFixado,
  setSnapshotFixado,
  PremiacaoIndisponivel,
  MIN_LINHAS_SNAPSHOT,
  MESES_MIN_VITORIA,
  REGIOES,
} 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
}

premiacao.get('/', async (c) => {
  try {
    const resultado = await apurarPremiacao(c.env)
    // Resultado congelado não muda até alguém repinar: pode cachear por mais tempo.
    const maxAge = resultado.congelado ? 86400 : 3600
    return c.json(resultado, 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,
      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 }