Notes

La publicitat s''importa: Advanced Ads cap a Payload

Dilluns curt i resolut: l''importador d''Advanced Ads porta zones, campanyes

3 min de lectura

El dilluns curt

Tres commits, tots de migració de publicitat (F6c): l'importador d'Advanced Ads (la publicitat del WordPress vell) ja porta les campanyes cap a Payload, amb el contacte de l'anunciant inclòs quan existeix. El camí fins aquí: el primer esbós del migrador i la connexió amb el WordPress de producció.

El contacte que no sempre hi és

El camp de contacte de l'anunciant és opcional: si el WordPress no en té, la campanya es crea igual. Si fos obligatori, cada campanya sense contacte hauria fallat.

pymysql no dóna text

La correcció que tallava la creació de campanyes: iso() esperava text ('2027-01-29 23:01:00'), però pymysql (la llibreria que parla amb MySQL, la base de dades) retorna datetime (el tipus data-hora) en llegir post_date. Un replace sobre un datetime no existeix, i la importació rebentava. Ara accepta les dues formes, i les comprovacions pròpies en tenen l'assert (una verificació que falla si no es compleix):

assert iso(_dt(2027, 1, 29, 23, 1)) == "2027-01-29T23:01:00"   # pymysql
assert iso("2027-01-29 23:01:00") == "2027-01-29T23:01:00"     # text

Comprovar-ho contra el Payload en viu

I per tancar, l'script de verificació post-import: llegeix les campanyes i banners reals del Payload i les enfronta amb l'origen WordPress. Migrar sense comprovar és migrar amb els dits creuats.

Què queda

F6c, tancat. La publicitat del WordPress vell ja té camí cap a Payload: zones, campanyes, banners i contacte d'anunciant.

La nit: subscripcions, butlletins i el salt a producció

El dilluns no s'acabava amb la publicitat. Entre les 19h i les 23h va caure la fase 7 sencera i la primera pedra de la 8.

Doble opt-in, no negociable

El formulari de subscripció ja no és un stub. Ara hi ha una col·lecció de subscriptors amb estat (pendent, confirmat, baixa), un token únic que només pot llegir l'admin i la IP truncada per GDPR: IPv4 a /24, IPv6 a /64. El POST /api/subscriure manté el contracte del stub del layout: resposta 200 sempre, per no deixar sondejar adreces. I la baixa no esborra el registre: queda com a auditoria.

Butlletins: composer, enviament i arxiu

El composer d'/admin/newsletter, enviament per SendGrid i l'arxiu públic a /butlletins (F7b). Amb el seu lot de migració:

DO $$ BEGIN
  CREATE TYPE "public"."enum_butlletins_estat" AS ENUM('esborrany', 'programat', 'enviat');
EXCEPTION WHEN duplicate_object THEN null; END $$;

CREATE TYPE IF NOT EXISTS no existeix a Postgres: el patró DO-block del repo és la manera. I dos clàssics: comentaris JS dins del template SQL que se n'anaven com a text literal (ara --), i un Set de relacions compartit entre l'array i la taula arrel a butlletins_rels, mateix patró que ja vam veure amb plans_laia_rels.

Detall de frontend que va costar la seva hora: l'arxiu de butlletins amb prerender d'ISR intentava parlar amb la base de dades durant el build. force-dynamic i fora. I un test que mentia: el del sitemap donava fals negatiu per SIGPIPE amb pipefail.

El servidor de producció, per escrit

F8a arrenca: docker-compose.prod.yml amb Caddy al TLS (80/443), l'app només escoltant a 127.0.0.1:3000, MinIO sense ports públics i els secrets via PANXING_SECRETS_DIR. El deploy-prod.sh fa pull → build (amb tag per poder fer rollback) → up → migrate → smoke. I export-to-prod.sh automatitza el runbook 09a: quan hi hagi VPS, executar-lo dos cops i mirar el smoke.

El tret final, molt del dia a dia: el guard d'usuaris de MinIO feia mc admin user list | awk | grep, i la imatge latest ha deixat de portar awk i grep. Ara:

if mc admin user info local "$S3_ACCESS_KEY_ID" >/dev/null 2>&1; then

F7 tancada. F8 encetada.