teqo-backup/scripts/seed-petition-pl-5594.mjs
Francisco Solla 5786bc57dc feat(petition): publica a petição do PL 5594/2026
A ferramenta de abaixo-assinado já existia, mas a petição do PL não tinha
caminho reproduzível fora do dump legado: produção só carregava as três
petições antigas editadas no admin. O conteúdo agora é versionado em
scripts/lib/petitionPl5594Content.mjs e provisionado por
pnpm seed:petition-pl-5594 — dry-run por padrão, create-if-missing por id,
Consent whatsapp-inscricao resolvido por chave (fail-closed) e guards de
escrita (PETITION_SEED_CONFIRM + TEQO_ENV casando o banco exato), sem
sobrescrever edição do admin.

O corpo traz os fatos do inteiro teor (autorização no registro, 5
exercícios de IR, sigilos bancário/fiscal/telefônico/de conexão, exclusão
do conteúdo das comunicações, auditoria, LGPD e divulgação pela Justiça
Eleitoral), a motivação de 18-19/10 sem acusar crime e o pedido sem
prometer aprovação. A copy hardcoded do bloco do formulário deixa de falar
em 'campanha'/'luta dos trabalhadores' e vira genérica.

Prova: unit full 475 arquivos/5238 testes verdes, lint/typecheck/format
limpos, seed aplicado e reaplicado em DB temporário (criação + no-op) e
smoke real no navegador (página 200, consent, assinatura nº 1). Sem
migration, collection, Consent novo ou mudança no fluxo de assinatura.
2026-10-22 00:51:20 -03:00

203 lines
7.1 KiB
JavaScript

/**
* S48 — provisions the PL 5594/2026 petition (`pl-5594-2026`).
*
* The petition is editorial content: production historically only had the three
* petitions restored from the old platform (edited in the admin), so a fresh
* database has no reproducible path to the new one. This seed writes the content
* versioned in `scripts/lib/petitionPl5594Content.mjs`.
*
* Contract:
* - dry-run (default): resolves the target/Consent and reports the plan; no write
* - `--apply`: creates the petition only when the id is missing. An existing row
* is never overwritten — after the first write the admin owns the content.
* - the Consent is the existing `whatsapp-inscricao` document, resolved by
* stable key (`getConsentByKey`), fail-closed when absent.
* - `--apply` outside a provably local dev database requires
* `PETITION_SEED_CONFIRM=1` and a `TEQO_ENV` matching the exact database
* (staging → `teqo_staging`, production → `teqo_1313`).
*
* After a production seed the page may still serve a cached 404 until the server
* restarts/redeploys (or the petition is saved once in the admin): the seed runs
* outside the Next runtime and cannot bust `document_petition:<id>`.
*
* Usage:
* pnpm seed:petition-pl-5594
* pnpm seed:petition-pl-5594 --apply
* TEQO_ENV=production PETITION_SEED_CONFIRM=1 pnpm seed:petition-pl-5594 --apply
*/
import { convertHTMLToLexical, editorConfigFactory } from '@payloadcms/richtext-lexical'
import { JSDOM } from 'jsdom'
import { getPayload } from 'payload'
import {
PETITION_BODY_HTML,
PETITION_BODY_SECTIONS,
PETITION_CONSENT_KEY,
PETITION_FORM_SUBTITLE,
PETITION_FORM_TITLE,
PETITION_ID,
PETITION_SUBTITLE,
PETITION_TITLE,
} from './lib/petitionPl5594Content.mjs'
import {
assertEnvironmentDatabaseTarget,
assertWriteConfirm,
databaseName,
databaseTarget,
dieWithLabel,
loadCliEnv,
requiresWriteConfirm,
TEQO_ENV_DATABASE_BY_ENV,
} from './lib/cli.mjs'
loadCliEnv()
const die = dieWithLabel('seed:petition-pl-5594')
const WRITE_CONFIRM_FLAG = 'PETITION_SEED_CONFIRM'
const HELP = `
Uso: pnpm seed:petition-pl-5594 [--apply]
Cria a petição "${PETITION_ID}" (abaixo-assinado do PL 5594/2026), ativa e
publicada, com o Consent existente "${PETITION_CONSENT_KEY}" resolvido por chave.
Modos:
(sem flag) plano/dry-run — alvo, Consent e estado atual; sem escrita
--apply cria a petição se o id não existir (nunca sobrescreve)
Ambiente:
DATABASE_URL alvo (obrigatório em todos os modos)
--apply em alvo não-local/produção exige ${WRITE_CONFIRM_FLAG}=1 e TEQO_ENV
casando o banco exato (staging=teqo_staging | production=teqo_1313).
`
/**
* `draft: true` is what makes the lookup honest: a draft-only row with the same
* id (the admin create view generates one during render) would otherwise be
* invisible and the create would die on a duplicate key instead of no-op.
*/
const findPetition = async (payload, id) => {
const result = await payload.find({
collection: 'petition',
where: { id: { equals: id } },
draft: true,
limit: 1,
depth: 0,
pagination: false,
})
return result.docs[0] ?? null
}
async function main() {
const argv = process.argv.slice(2)
if (argv.includes('--help') || argv.includes('-h')) {
console.log(HELP)
process.exit(0)
}
const unknown = argv.find((arg) => arg !== '--apply')
if (unknown)
die(`argumento desconhecido: ${unknown} — uso: pnpm seed:petition-pl-5594 [--apply].`)
const apply = argv.includes('--apply')
if (!process.env.DATABASE_URL) die('DATABASE_URL não definida; recusando continuar.')
console.log(
`[seed:petition-pl-5594] alvo: ${databaseTarget()} | modo: ${apply ? 'apply' : 'plan'}`,
)
if (apply) {
assertWriteConfirm({
label: 'seed:petition-pl-5594',
flag: WRITE_CONFIRM_FLAG,
command: 'pnpm seed:petition-pl-5594 --apply',
})
// A worktree dev database is provably local and follows the write-confirm
// policy (no flag). But the homeserver proxy rewrites the host to localhost,
// so a local host carrying a declared target name (teqo_1313/teqo_staging)
// must still declare the exact environment — the name is the discriminator.
const targetName = databaseName(process.env.DATABASE_URL)
const declaredTargets = Object.values(TEQO_ENV_DATABASE_BY_ENV)
if (requiresWriteConfirm() || declaredTargets.includes(targetName)) {
assertEnvironmentDatabaseTarget()
}
}
const config = (await import('../src/payload.config.ts')).default
const payload = await getPayload({ config })
const existing = await findPetition(payload, PETITION_ID)
if (existing) {
console.log(
`[seed:petition-pl-5594] ${PETITION_ID}: existe — enabled=${Boolean(existing.enabled)} ` +
`_status=${existing._status ?? '(sem drafts)'}; não sobrescreve (o admin é dono).`,
)
if (!existing.enabled || existing._status !== 'published') {
console.log(
'[seed:petition-pl-5594] atenção: a petição existe mas não está ativa/publicada; ' +
'ajuste no admin (o seed nunca sobrescreve).',
)
}
process.exit(0)
}
const { getConsentByKey } = await import('../src/utilities/campaignConsent.ts')
const consent = await getConsentByKey(payload, PETITION_CONSENT_KEY)
if (!consent) {
die(
`Consent "${PETITION_CONSENT_KEY}" ausente — a petição não pode ser criada sem ele ` +
'(fail-closed). Rode `pnpm db:seed:minimal` no ambiente local ou confira o Consent ' +
'de produção antes de aplicar.',
)
}
console.log(
`[seed:petition-pl-5594] Consent "${PETITION_CONSENT_KEY}": #${consent.id} (resolvido por chave)`,
)
console.log(
`[seed:petition-pl-5594] ${PETITION_ID}: ausente | ${PETITION_BODY_SECTIONS.length} bloco(s) de corpo | ` +
`título "${PETITION_TITLE}"`,
)
if (!apply) {
console.log('\n[seed:petition-pl-5594] dry-run — use --apply para criar a petição.')
process.exit(0)
}
const editorConfig = await editorConfigFactory.default({ config: payload.config })
const body = convertHTMLToLexical({ editorConfig, html: PETITION_BODY_HTML, JSDOM })
const created = await payload.create({
collection: 'petition',
data: {
id: PETITION_ID,
title: PETITION_TITLE,
subtitle: PETITION_SUBTITLE,
enabled: true,
body,
form: {
title: PETITION_FORM_TITLE,
subtitle: PETITION_FORM_SUBTITLE,
consent: consent.id,
},
_status: 'published',
},
depth: 0,
// Operator CLI: the create must write the petition even though the
// collection's create access is admin-only (same intent as the seeds).
overrideAccess: true,
})
console.log(
`[seed:petition-pl-5594] ${PETITION_ID}: criada (#${created.id}) ` +
`enabled=${Boolean(created.enabled)} _status=${created._status ?? '(sem drafts)'}.`,
)
console.log(
'[seed:petition-pl-5594] Em produção, reinicie/redeploye o container (ou salve a petição uma vez ' +
'no admin) antes de divulgar: o seed não busta o cache document_petition da rota.',
)
process.exit(0)
}
main().catch((error) => {
die(error?.message || String(error))
})