runv-server

server tooling for runv.club
Log | Files | Refs | README

runv_landing_sync.py (3195B)


      1 """
      2 Sincronização da landing pública após alterações a ``users.json``.
      3 
      4 Invoca ``site/genlanding.py --sync-public-only`` (cópia de ``site/public/`` +
      5 ``data/members.json``). Partilhado por create_runv_user, update_user e del-user.
      6 """
      7 
      8 from __future__ import annotations
      9 
     10 import json
     11 import logging
     12 import subprocess
     13 import sys
     14 from pathlib import Path
     15 
     16 _SCRIPT_DIR = Path(__file__).resolve().parent
     17 _REPO_ROOT = _SCRIPT_DIR.parent.parent
     18 
     19 
     20 def genlanding_sync_command(
     21     *,
     22     document_root: Path,
     23     users_json: Path,
     24     homes_root: Path | None = None,
     25 ) -> list[str]:
     26     """Comando completo para ``site/genlanding.py --sync-public-only`` (lista para subprocess)."""
     27     script = _REPO_ROOT / "site" / "genlanding.py"
     28     cmd: list[str] = [
     29         sys.executable,
     30         str(script),
     31         "--sync-public-only",
     32         "--document-root",
     33         str(document_root),
     34         "--members-users-json",
     35         str(users_json),
     36     ]
     37     if homes_root is not None:
     38         cmd.extend(["--members-homes-root", str(homes_root)])
     39     return cmd
     40 
     41 
     42 def try_sync_landing_via_genlanding(
     43     *,
     44     document_root: Path,
     45     users_json: Path,
     46     homes_root: Path | None,
     47     log: logging.Logger,
     48 ) -> tuple[bool, int | None]:
     49     """
     50     Copia site/public → DocumentRoot e regenera data/members.json (genlanding.py --sync-public-only).
     51     Falhas são apenas registadas — não aborta o chamador.
     52     Devolve (sucesso, número de membros no JSON público ou None se não foi possível contar).
     53     """
     54     script = _REPO_ROOT / "site" / "genlanding.py"
     55     if not script.is_file():
     56         log.warning(
     57             "genlanding.py não encontrado em %s; landing não sincronizada",
     58             script,
     59         )
     60         return False, None
     61     cmd = genlanding_sync_command(
     62         document_root=document_root,
     63         users_json=users_json,
     64         homes_root=homes_root,
     65     )
     66     out = document_root / "data" / "members.json"
     67     try:
     68         r = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
     69         combined = ((r.stdout or "") + "\n" + (r.stderr or "")).strip()
     70         if r.returncode != 0:
     71             log.warning(
     72                 "genlanding --sync-public-only terminou com código %s: %s",
     73                 r.returncode,
     74                 combined[:2000] if combined else "(sem saída)",
     75             )
     76             return False, None
     77         log.info("landing sincronizada (site/public + members.json) em %s", document_root)
     78         if combined:
     79             log.debug("genlanding sync: %s", combined[:1500])
     80         n_public: int | None = None
     81         try:
     82             raw = out.read_text(encoding="utf-8")
     83             parsed = json.loads(raw)
     84             if isinstance(parsed, list):
     85                 n_public = len(parsed)
     86                 log.info("constelação: %s membro(s) no dataset público (%s)", n_public, out)
     87         except (OSError, json.JSONDecodeError, TypeError) as ex:
     88             log.warning("members.json após sync não foi possível validar: %s", ex)
     89         return True, n_public
     90     except (OSError, subprocess.TimeoutExpired) as e:
     91         log.warning("falha ao executar genlanding --sync-public-only: %s", e)
     92         return False, None