initial: split from gov-agreg — vreau.digital standalone platform

Moved from gov-agreg/src/pages/achizitii/* to root (drop prefix).
- 22 pages migrated, 127 files total
- All internal links: /achizitii/X → /X (176 occurrences fixed)
- AchizitiiLayout subnav rewritten: /X paths, top-right link to vreaudigital.ro hub
- BaseLayout new (vreau.digital branding, OG tags, site URL)
- astro.config.mjs: site https://vreau.digital, server output (was static)
- docker-compose: port 5096 (vreaudigital is 5095), container vreau-digital
- deploy.sh: paths /opt/vreau-digital, log /var/log/vreau-digital-deploy.log

Backend shared with gov-agreg:
- PostgreSQL satra (same schemas: seap, firms, anaf, anre, ...)
- Photon, Martin tiles
- Infisical /vreaudigital path (DATABASE_URL etc. shared)

build: PASS (npx astro check 0 errors, npm run build 5s vite + 10s server)
This commit is contained in:
Claude VM
2026-05-13 00:10:32 +03:00
commit a6c03a091e
352 changed files with 75295 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
#!/bin/bash
# Daily data-freshness heartbeat for vreaudigital.ro
# - Queries max(fetched_at) per primary table across 17 schemas
# - Compares against per-source expected cadence (days)
# - Posts a webhook payload if any source is stale beyond threshold
# - Always exits 0 (alerts are signal, not error — cron noise budget = 1 alert/day)
#
# Run from satra cron at 07:00 daily.
# Designed to be paranoid-safe: never echoes the DB password, never fails
# loud on transient DB blips (only fails when the heartbeat itself can't run).
set -uo pipefail
LOG=/var/log/vreaudigital-heartbeat.log
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
WEBHOOK_URL="https://n8n.beletage.ro/webhook/satra-backup-alert"
HOSTNAME_TAG="vreaudigital"
log "=== Heartbeat 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"')
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
# Per-source cadence query. Each row: source_label, expected_max_days, actual_gap_days,
# last_seen_date. Sources stuck at known long staleness (anaf datornici Q1 2016) are
# excluded — heartbeat noise budget is for fixable freshness, not known constants.
QUERY=$(cat <<'SQL'
WITH probes AS (
SELECT 'seap.announcements' AS label, 2 AS expected_days, max(publication_date)::date AS last_seen FROM seap.announcements
UNION ALL
SELECT 'seap.wsp_sync_state', 1, max(last_run_at)::date FROM seap.wsp_sync_state
UNION ALL
SELECT 'seap.sync_state(da)', 30, max(updated_at)::date FROM seap.sync_state WHERE source='da'
UNION ALL
SELECT 'firms.entities', 100, max(updated_at)::date FROM firms.entities
UNION ALL
SELECT 'firms.financials', 400, max(fetched_at)::date FROM firms.financials
UNION ALL
SELECT 'fonduri.beneficiar_anunt', 7, max(data_publicare)::date FROM fonduri.beneficiar_anunt
UNION ALL
SELECT 'fonduri.afir_plati', 365, max(fetched_at)::date FROM fonduri.afir_plati
UNION ALL
SELECT 'regas.ajutoare', 45, max(fetched_at)::date FROM regas.ajutoare
UNION ALL
SELECT 'aep.donatii_pj', 60, max(fetched_at)::date FROM aep.donatii_pj
UNION ALL
SELECT 'ani.declaratii', 400, max(fetched_at)::date FROM ani.declaratii
UNION ALL
SELECT 'bugetar.entitate', 60, max(updated_at)::date FROM bugetar.entitate
UNION ALL
SELECT 'anre.licente', 14, max(fetched_at)::date FROM anre.licente
UNION ALL
SELECT 'ancom.operatori', 14, max(fetched_at)::date FROM ancom.operatori
UNION ALL
SELECT 'cnsc.decizii', 14, max(fetched_at)::date FROM cnsc.decizii
UNION ALL
SELECT 'cnas.furnizori', 60, max(fetched_at)::date FROM cnas.furnizori
UNION ALL
SELECT 'asf.entitati', 14, max(fetched_at)::date FROM asf.entitati
UNION ALL
SELECT 'aaas.firme', 30, max(fetched_at)::date FROM aaas.firme
UNION ALL
SELECT 'curteacont.rapoarte', 14, max(fetched_at)::date FROM curteacont.rapoarte
UNION ALL
SELECT 'apia.fermieri', 60, max(fetched_at)::date FROM apia.fermieri
UNION ALL
SELECT 'gnm.comunicate', 14, max(fetched_at)::date FROM gnm.comunicate
)
SELECT label, expected_days,
-- clamp future dates (TED publication-date can be in the future) and
-- treat NULL last_seen as ancient (empty table → alert).
-- NB: LEAST(NULL, x) = x in PG (returns NULL only if all args NULL),
-- so explicit CASE for NULL handling.
CASE WHEN last_seen IS NULL THEN 9999
ELSE (now()::date - LEAST(last_seen, now()::date)) END AS gap_days,
COALESCE(last_seen::text, 'NEVER') AS last_seen,
CASE WHEN last_seen IS NULL THEN 'STALE'
WHEN (now()::date - LEAST(last_seen, now()::date)) > expected_days THEN 'STALE'
ELSE 'OK' END AS status
FROM probes
ORDER BY CASE WHEN last_seen IS NULL THEN 9999
ELSE (now()::date - LEAST(last_seen, now()::date)) END DESC;
SQL
)
OUT=$(psql -v ON_ERROR_STOP=1 -A -F$'\t' -t -c "$QUERY" 2>&1) || {
log "ERROR: psql failed — heartbeat skipped this run"
log "$OUT"
exit 0
}
unset PGPASSWORD
STALE_LIST=$(echo "$OUT" | awk -F'\t' '$5=="STALE" { printf "%s (gap=%sd, expected≤%sd, last=%s)\n", $1, $3, $2, $4 }')
STALE_COUNT=$(echo -n "$STALE_LIST" | grep -c . || true)
TOTAL=$(echo -n "$OUT" | grep -c . || true)
log "Probed $TOTAL sources, $STALE_COUNT stale"
echo "$OUT" | awk -F'\t' '{ printf " %-30s %s gap=%sd last=%s\n", $1, $5, $3, $4 }' | tee -a "$LOG"
if [ "$STALE_COUNT" -gt 0 ]; then
log "ALERT — posting to webhook"
PAYLOAD=$(jq -nc \
--arg s "STALE" \
--arg h "$HOSTNAME_TAG" \
--argjson c "$STALE_COUNT" \
--argjson t "$TOTAL" \
--arg d "$STALE_LIST" \
'{status:$s, host:$h, service:"data-heartbeat", stale_count:$c, total:$t, details:$d}')
curl -sS -X POST -H "Content-Type: application/json" --max-time 30 \
-d "$PAYLOAD" "$WEBHOOK_URL" >/dev/null 2>&1 || log "webhook POST failed (non-fatal)"
fi
log "=== Done ==="
exit 0