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.