T-HUB - Guía de validación de cambios ===================================== Para: quien revise y valide los cambios de T-HUB antes de llevarlos a upstream (gitlab.moxexley.com/mediatools/tacticalmediahub) o a producción. Base: upstream main a6843e9. Commits a revisar (en orden): feat: wire the Instagram connector into connect and publishing feat: outreach contacts - track hand-written DMs per collective chore(db): force row-level security on media_assets style / docs: estilo de la web, logo y documentación Cada commit se puede revisar y aplicar por separado. 0. PREPARACIÓN -------------- corepack enable pnpm install --frozen-lockfile pnpm exec turbo run build typecheck --filter=@tactical-media-broadcast-hub/api... Para los tests que usan Postgres hacen falta las variables *_IT_DATABASE_URL de cada suite; sin ellas se saltan (comportamiento de upstream). Lint: apps/api ya tenía 54 errores antes de estos cambios y sigue teniendo los mismos 54. Los archivos tocados pasan eslint sin errores. 1. INSTAGRAM ---------------------- Qué cambia - createInstagramConnector registrado en apps/api/src/connections/connector-registry.ts y en connectorFor de apps/api/src/publishing/publishing.service.ts. - Política de publicación instagram: { idempotency: "none" } en packages/connector-runtime/src/policy.ts (Graph no tiene clave de idempotencia; un envío dudoso no se repite). - Las credenciales de la app de Meta se leen SOLO de INSTAGRAM_CLIENT_ID / INSTAGRAM_CLIENT_SECRET. Se quitan los campos clientId/clientSecret del formulario y ya no se copian al vault. - El proveedor sale desactivado (connectEnabled=false) sin esas dos variables, con una nota que lo explica. - Si la persona autoriza varias cuentas de Instagram en el diálogo de Meta, la conexión falla en vez de elegir la primera. Tests automáticos pnpm --filter @tactical-media-broadcast-hub/connector-instagram test esperado: 37 pass cd apps/api && npx vitest run src/connections esperado: 18 pass (incluye "offers Instagram only with the Hub's Facebook app configured, and keeps the app secret out of the browser") Prueba manual (entorno de pruebas, app de Meta en modo desarrollo) 1. Sin variables: GET /connections/providers -> instagram enabled=false. 2. Con INSTAGRAM_CLIENT_ID/SECRET: enabled=true. 3. En la app de Meta, Valid OAuth Redirect URI = ${API_URL}/connections/callback (exacto). 4. Canales -> Conectar Instagram con una cuenta Tester Business/Creator vinculada a una página -> vuelve con connect=success y la conexión sale healthy con @usuario. 5. Autorizar dos cuentas a la vez -> connect=failure. Límites conocidos (no resueltos en este commit) - Publicar imágenes NO funciona todavía: Meta descarga la imagen de una URL pública y los medios están cifrados en el almacén. Hace falta un endpoint temporal que sirva el JPEG descifrado (TTL corto, solo borradores aprobados). Además la regla del proyecto es que los bytes no pasen por la API. Decisión de diseño pendiente. - refreshCredentials usa grant_type=ig_refresh_token contra graph.facebook.com, que es el método de "Instagram Login", no de Facebook Login. Comprobar con un token real; posible caducidad a 60 días. 2. OUTREACH --------------------- Qué cambia - Migración 041_outreach.sql: tabla outreach_contacts con RLS ENABLE+FORCE y WITH CHECK, índice único (workspace, platform, lower(handle)), trigger de workspace inmutable, permisos outreach.view (Owner, Admin, Editor, Moderator) y outreach.manage (Owner, Admin, Editor). - API: GET/POST/PATCH/DELETE /outreach/contacts (apps/api/src/outreach). Importación de hasta 500 por petición; las cuentas ya existentes se saltan. La auditoría guarda altas, bajas y cambios de estado, nunca el texto del mensaje. - Web: /outreach (barra lateral y "More"). Importar CSV, plantilla con {name} y {handle}, filtros, "Copy & open profile", contador diario con aviso a partir de 20. - El Hub no envía mensajes. No hay llamadas a APIs de redes en este módulo. Ver README, sección "Panel Outreach", para el motivo. Tests automáticos cd apps/api && npx vitest run src/outreach esperado: 8 pass pnpm --filter @tactical-media-broadcast-hub/web test:outreach esperado: 4 pass Verificado además (fuera del repo): la migración 041 y todas las consultas del servicio se ejecutaron sobre PGlite (Postgres compilado a WASM) como rol sin privilegios con app.workspace_id fijado: aislamiento entre colectivos, salto de duplicados en mayúsculas/minúsculas, sellos de sent_at/replied_at, vuelta a "todo" limpia los sellos. Prueba manual 1. Aplicar migraciones (src/db/migrate.cli.ts). 2. Como Editor: importar name,platform,handle,profileUrl,category,language Prueba,bluesky,prueba.bsky.social,https://bsky.app/profile/prueba.bsky.social,test,es -> "1 contacts added". Reimportar -> "0 added, 1 already on the list". 3. Plantilla "Hola {name}" -> el contacto pendiente recibe "Hola Prueba". 4. Copy & open profile -> abre el perfil en otra pestaña con el texto en el portapapeles. 5. Mark sent -> estado sent y sello de fecha; aparece outreach.sent en /audit sin el texto del mensaje. 6. Como Analyst (sin outreach.view) -> GET /outreach/contacts da 403. 7. Desde otro colectivo -> la lista no muestra los contactos del primero y PATCH sobre su id da 404. 8. profileUrl con http:// o javascript: -> 400. 3. RLS EN media_assets ---------------------- Qué cambia - 042_media_assets_rls.sql: ENABLE + FORCE RLS y política con WITH CHECK. - Las consultas a media_assets (media.service.ts, publishing.service.ts) filtran también por app.workspace_id. Verificación - cd apps/api && npx vitest run src/media src/publishing esperado: 114 pass, 49 skipped (necesitan Postgres). - En la base de datos, después de migrar: SELECT relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname = 'media_assets'; -> t, t - Prueba manual: subir un medio en el colectivo A y pedir GET /media/ con sesión del colectivo B -> 404. 4. ESTILO Y LOGO ---------------- - apps/web/src/app/globals.css: paleta de acampada26s.net. Fondo blanco, texto y bordes negros, botones negros con sombra neón desplazada, foco y elemento activo en neón #00e5ff. Títulos h1/h2 en Anton en mayúsculas con la sombra neón escalonada de la web. - Iconos de navegación en negro; iconos de redes con trazo neón. - Logo: tres tiendas iglú cableadas a un switch con antena, en el estilo a tinta del logo de la acampada (docs/brand/t-hub.svg; t-hub-lockup.svg/png con el nombre en Anton convertido a trazos). Componente LogoMark sustituido; nombre visible "T-HUB". - Fuente Anton en apps/web/public/fonts (licencia SIL OFL incluida). - Página pública de buzón (/submit/[token]) en claro. Revisado con capturas (Chromium sin interfaz, modo offline con datos de ejemplo): Streams, Compose, Outreach, Channels, Approvals, Onboarding, /submit en escritorio y móvil. Falta revisar Control Room y Calendar con datos reales. 5. SEGURIDAD ------------ La revisión de seguridad se entrega en privado a quien mantiene el despliegue. Los fallos se comunican por canal privado, no en issues.