#!/bin/bash # Nightly refresh of seap materialized views. # Run from satra cron at 04:00 — peak DB idle window. # # Sources DATABASE_URL via Infisical Machine Identity (same as the # vreaudigital container). Never echoes the value. set -euo pipefail LOG=/var/log/vreaudigital-mvs.log log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG"; } log "=== Materialized view refresh started ===" if [ ! -f /opt/vreaudigital/.infisical-mi ]; then log "FATAL: /opt/vreaudigital/.infisical-mi missing" exit 1 fi # shellcheck disable=SC1091 source /opt/vreaudigital/.infisical-mi TOKEN=$(infisical login \ --method=universal-auth \ --domain="$INFISICAL_API_URL" \ --client-id="$INFISICAL_CLIENT_ID" \ --client-secret="$INFISICAL_CLIENT_SECRET" \ --silent --plain) DATABASE_URL=$(infisical run \ --domain="$INFISICAL_API_URL" \ --projectId="$INFISICAL_PROJECT_ID" \ --env="$INFISICAL_ENV" \ --path="$INFISICAL_PATH" \ --silent --token="$TOKEN" \ -- sh -c 'echo "$DATABASE_URL"') # Parse URL into PG* env vars and discard URL — psql with the URL on the command # line leaks the password to anyone running `ps aux` (incident 2026-05-07). DB=$(echo "$DATABASE_URL" | sed -E 's/[?&]schema=[^&]*//; s/\?$//') export PGUSER=$(echo "$DB" | sed -E 's|^postgresql://([^:]+):.*|\1|') export PGPASSWORD=$(echo "$DB" | sed -E 's|^postgresql://[^:]+:([^@]+)@.*|\1|') export PGHOST=$(echo "$DB" | sed -E 's|^postgresql://[^@]+@([^:/]+).*|\1|') export PGPORT=$(echo "$DB" | sed -E 's|^postgresql://[^@]+@[^:]+:([0-9]+)/.*|\1|') export PGDATABASE=$(echo "$DB" | sed -E 's|^postgresql://[^@]+@[^/]+/([^?]+).*|\1|') unset DATABASE_URL TOKEN DB START=$(date +%s) psql -v ON_ERROR_STOP=1 <<'SQL' 2>&1 | tee -a "$LOG" \timing on REFRESH MATERIALIZED VIEW CONCURRENTLY seap.uat_procurement_stats; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.uat_kpi; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_authority_concentration; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_cpv_median_value; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_top_cpv_divisions; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_top_suppliers; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_top_authorities; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_recurrent_pairs; REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_supplier_cpv_share; -- Cross-source MVs (added 2026-05-11 after backfills) REFRESH MATERIALIZED VIEW CONCURRENTLY cnsc.mv_per_authority_cui; REFRESH MATERIALIZED VIEW CONCURRENTLY cnsc.mv_per_contestator_cui; REFRESH MATERIALIZED VIEW CONCURRENTLY anre.mv_licente_per_cui; REFRESH MATERIALIZED VIEW CONCURRENTLY ancom.mv_operatori_per_cui; REFRESH MATERIALIZED VIEW CONCURRENTLY asf.mv_entitati_per_cui; REFRESH MATERIALIZED VIEW CONCURRENTLY aaas.mv_per_cui; -- Red-flags KPI snapshot (043_red_flags_kpi_snapshot.sql) SELECT public_kpi.refresh_red_flags_counts(); -- Red-flags previews snapshot (044_red_flags_previews_snapshot.sql) — top-5 -- rows per recipe; landing reads as a single SELECT instead of awaiting 14 -- live cross-source queries (~17s → ~5ms). SELECT public_kpi.refresh_red_flags_previews(); -- Cauta default-browse facets+totals snapshot (046) — short-circuits the 6 -- parallel facet aggregates when no filter is set (~1.9s → ~50ms). SELECT public_kpi.refresh_cauta_defaults(); SQL END=$(date +%s) log "=== Done in $((END-START))s ==="