Upload 10 files
Browse files- app.py +20 -3
- compliance_client.py +48 -1
- config.yaml +7 -3
- mobile_verification.py +32 -2
- persistent_data_gate.py +76 -1
app.py
CHANGED
|
@@ -3,6 +3,7 @@ import yaml
|
|
| 3 |
import shutil
|
| 4 |
from pathlib import Path
|
| 5 |
import uuid
|
|
|
|
| 6 |
try:
|
| 7 |
import tomllib
|
| 8 |
except ModuleNotFoundError: # Py<3.11
|
|
@@ -73,7 +74,7 @@ def _load_yaml(path="config.yaml") -> dict:
|
|
| 73 |
CFG = _load_yaml("config.yaml")
|
| 74 |
|
| 75 |
# Ajuste de variables según tu esquema YAML
|
| 76 |
-
# Para la interfaz demo, usaremos
|
| 77 |
DATA_DIR = "temp"
|
| 78 |
BACKEND_BASE_URL = CFG.get("api", {}).get("base_url", "http://localhost:8000")
|
| 79 |
TTS_URL = "https://veureu-tts.hf.space" # Forzar URL correcta
|
|
@@ -82,7 +83,11 @@ print(f"🔧 Tipo de TTS_URL: {type(TTS_URL)}")
|
|
| 82 |
print(f"🔧 Longitud de TTS_URL: {len(TTS_URL)}")
|
| 83 |
print(f"🔧 TTS_URL repr: {repr(TTS_URL)}")
|
| 84 |
USE_MOCK = bool(CFG.get("app", {}).get("use_mock", False)) # si no la tienes en el yaml, queda False
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
API_TOKEN = CFG.get("api", {}).get("token") or os.getenv("API_SHARED_TOKEN")
|
| 87 |
|
| 88 |
# Cliente del backend (engine)
|
|
@@ -98,7 +103,19 @@ DB_PATH = os.path.join(DATA_DIR, "users.db")
|
|
| 98 |
set_db_path(DB_PATH)
|
| 99 |
|
| 100 |
# Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB
|
| 101 |
-
set_blockchain_enabled(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
init_schema()
|
| 104 |
|
|
|
|
| 3 |
import shutil
|
| 4 |
from pathlib import Path
|
| 5 |
import uuid
|
| 6 |
+
from datetime import datetime
|
| 7 |
try:
|
| 8 |
import tomllib
|
| 9 |
except ModuleNotFoundError: # Py<3.11
|
|
|
|
| 74 |
CFG = _load_yaml("config.yaml")
|
| 75 |
|
| 76 |
# Ajuste de variables según tu esquema YAML
|
| 77 |
+
# Para la interfaz demo, usaremos sempre la carpeta "temp" local
|
| 78 |
DATA_DIR = "temp"
|
| 79 |
BACKEND_BASE_URL = CFG.get("api", {}).get("base_url", "http://localhost:8000")
|
| 80 |
TTS_URL = "https://veureu-tts.hf.space" # Forzar URL correcta
|
|
|
|
| 83 |
print(f"🔧 Longitud de TTS_URL: {len(TTS_URL)}")
|
| 84 |
print(f"🔧 TTS_URL repr: {repr(TTS_URL)}")
|
| 85 |
USE_MOCK = bool(CFG.get("app", {}).get("use_mock", False)) # si no la tienes en el yaml, queda False
|
| 86 |
+
|
| 87 |
+
COMPLIANCE_CFG = CFG.get("compliance", {}) or {}
|
| 88 |
+
PRIVATE_BLOCKCHAIN_ENABLE = bool(COMPLIANCE_CFG.get("private_blockchain_enable", False))
|
| 89 |
+
PUBLIC_BLOCKCHAIN_ENABLE = bool(COMPLIANCE_CFG.get("public_blockchain_enable", False))
|
| 90 |
+
MONTHLY_DIGEST_ENABLED = bool(COMPLIANCE_CFG.get("monthly_digest_enabled", False))
|
| 91 |
API_TOKEN = CFG.get("api", {}).get("token") or os.getenv("API_SHARED_TOKEN")
|
| 92 |
|
| 93 |
# Cliente del backend (engine)
|
|
|
|
| 103 |
set_db_path(DB_PATH)
|
| 104 |
|
| 105 |
# Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB
|
| 106 |
+
set_blockchain_enabled(PRIVATE_BLOCKCHAIN_ENABLE)
|
| 107 |
+
|
| 108 |
+
# Si està habilitat el registre públic i el digest mensual, publicar al
|
| 109 |
+
# principi de cada mes un digest global via servei de compliance.
|
| 110 |
+
if PUBLIC_BLOCKCHAIN_ENABLE and MONTHLY_DIGEST_ENABLED:
|
| 111 |
+
try:
|
| 112 |
+
today = datetime.utcnow().date()
|
| 113 |
+
if today.day == 1:
|
| 114 |
+
period = today.strftime("%Y-%m")
|
| 115 |
+
compliance_client.publish_monthly_digest(period)
|
| 116 |
+
except Exception:
|
| 117 |
+
# No bloquejar l'app per errors de blockchain/compliance
|
| 118 |
+
pass
|
| 119 |
|
| 120 |
init_schema()
|
| 121 |
|
compliance_client.py
CHANGED
|
@@ -92,6 +92,20 @@ class ComplianceClient:
|
|
| 92 |
|
| 93 |
def is_authenticated(self) -> bool:
|
| 94 |
"""Verifica si el usuario actual está autenticado"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
if "auth_token" not in st.session_state:
|
| 96 |
return False
|
| 97 |
|
|
@@ -113,6 +127,11 @@ class ComplianceClient:
|
|
| 113 |
|
| 114 |
def get_current_user(self) -> Optional[str]:
|
| 115 |
"""Obtiene email del usuario actual"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
if self.is_authenticated():
|
| 117 |
return st.session_state.get("current_user", {}).get("email")
|
| 118 |
return None
|
|
@@ -122,6 +141,11 @@ class ComplianceClient:
|
|
| 122 |
print("[OAuth] show_login_button llamado") # Forzar salida a log del Space
|
| 123 |
logger.info("[OAuth] show_login_button llamado")
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
# Verificar si OAuth está configurado en el servicio
|
| 126 |
try:
|
| 127 |
health_response = self._make_request("GET", "/")
|
|
@@ -162,7 +186,7 @@ class ComplianceClient:
|
|
| 162 |
st.markdown(f"""
|
| 163 |
### 🔐 Iniciar sessió
|
| 164 |
|
| 165 |
-
Per continuar,
|
| 166 |
|
| 167 |
<a href="{login_url}" target="_top">
|
| 168 |
<button style="
|
|
@@ -229,6 +253,29 @@ class ComplianceClient:
|
|
| 229 |
logger.error("[OAuth] login_url és None: comprova COMPLIANCE_SERVICE_URL i l'estat del servei de compliance")
|
| 230 |
|
| 231 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
def _show_demo_login(self) -> bool:
|
| 234 |
"""Muestra formulario de login por SMS en ventana desplegable cuando OAuth no está configurado"""
|
|
|
|
| 92 |
|
| 93 |
def is_authenticated(self) -> bool:
|
| 94 |
"""Verifica si el usuario actual está autenticado"""
|
| 95 |
+
# Primero verificar si hay autenticación tradicional
|
| 96 |
+
if 'user' in st.session_state and st.session_state.user:
|
| 97 |
+
logger.info(f"Usuario ya autenticado tradicionalmente: {st.session_state.user.get('username', 'unknown')}")
|
| 98 |
+
# Crear token automático para usuarios tradicionales
|
| 99 |
+
if 'auth_token' not in st.session_state:
|
| 100 |
+
st.session_state.auth_token = f"traditional_token_{int(time.time())}"
|
| 101 |
+
st.session_state.current_user = {
|
| 102 |
+
"email": f"{st.session_state.user.get('username', 'unknown')}@veureu.local",
|
| 103 |
+
"name": st.session_state.user.get('username', 'Unknown'),
|
| 104 |
+
"method": "traditional"
|
| 105 |
+
}
|
| 106 |
+
return True
|
| 107 |
+
|
| 108 |
+
# Si no hay autenticación tradicional, verificar por token
|
| 109 |
if "auth_token" not in st.session_state:
|
| 110 |
return False
|
| 111 |
|
|
|
|
| 127 |
|
| 128 |
def get_current_user(self) -> Optional[str]:
|
| 129 |
"""Obtiene email del usuario actual"""
|
| 130 |
+
# Primero verificar autenticación tradicional
|
| 131 |
+
if 'user' in st.session_state and st.session_state.user:
|
| 132 |
+
return f"{st.session_state.user.get('username', 'unknown')}@veureu.local"
|
| 133 |
+
|
| 134 |
+
# Si no, verificar autenticación por token
|
| 135 |
if self.is_authenticated():
|
| 136 |
return st.session_state.get("current_user", {}).get("email")
|
| 137 |
return None
|
|
|
|
| 141 |
print("[OAuth] show_login_button llamado") # Forzar salida a log del Space
|
| 142 |
logger.info("[OAuth] show_login_button llamado")
|
| 143 |
|
| 144 |
+
# Si ya hay usuario autenticado tradicionalmente, no mostrar login
|
| 145 |
+
if 'user' in st.session_state and st.session_state.user:
|
| 146 |
+
logger.info(f"[OAuth] Usuario ya autenticado tradicionalmente: {st.session_state.user.get('username', 'unknown')}")
|
| 147 |
+
return True
|
| 148 |
+
|
| 149 |
# Verificar si OAuth está configurado en el servicio
|
| 150 |
try:
|
| 151 |
health_response = self._make_request("GET", "/")
|
|
|
|
| 186 |
st.markdown(f"""
|
| 187 |
### 🔐 Iniciar sessió
|
| 188 |
|
| 189 |
+
Per continuar, necessitas iniciar sessió amb el teu compte de Google.
|
| 190 |
|
| 191 |
<a href="{login_url}" target="_top">
|
| 192 |
<button style="
|
|
|
|
| 253 |
logger.error("[OAuth] login_url és None: comprova COMPLIANCE_SERVICE_URL i l'estat del servei de compliance")
|
| 254 |
|
| 255 |
return False
|
| 256 |
+
|
| 257 |
+
# === VERIFICACIÓ PER TELÈFON / WHATSAPP ===
|
| 258 |
+
|
| 259 |
+
def create_phone_verification_session(
|
| 260 |
+
self,
|
| 261 |
+
*,
|
| 262 |
+
page: str,
|
| 263 |
+
user_id: Optional[str] = None,
|
| 264 |
+
email: Optional[str] = None,
|
| 265 |
+
action: Optional[str] = None,
|
| 266 |
+
) -> Optional[Dict[str, Any]]:
|
| 267 |
+
"""Crea una sessió de verificació de telèfon al backend de compliance.
|
| 268 |
+
|
| 269 |
+
Endpoint: POST /phone_verification/create_session
|
| 270 |
+
"""
|
| 271 |
+
|
| 272 |
+
payload: Dict[str, Any] = {
|
| 273 |
+
"page": page,
|
| 274 |
+
"user_id": user_id,
|
| 275 |
+
"email": email,
|
| 276 |
+
"action": action,
|
| 277 |
+
}
|
| 278 |
+
return self._make_request("POST", "/phone_verification/create_session", payload)
|
| 279 |
|
| 280 |
def _show_demo_login(self) -> bool:
|
| 281 |
"""Muestra formulario de login por SMS en ventana desplegable cuando OAuth no está configurado"""
|
config.yaml
CHANGED
|
@@ -5,6 +5,8 @@ app:
|
|
| 5 |
# tamaño máximo de subida en MB
|
| 6 |
max_upload_mb: 500
|
| 7 |
|
|
|
|
|
|
|
| 8 |
api:
|
| 9 |
# URL pública de tu space engine (puedes interpolar la var de entorno)
|
| 10 |
base_url: "${API_BASE_URL}"
|
|
@@ -39,7 +41,9 @@ ui:
|
|
| 39 |
page_icon: "🎧"
|
| 40 |
wide_layout: true
|
| 41 |
|
| 42 |
-
|
| 43 |
-
#
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
|
|
|
|
| 5 |
# tamaño máximo de subida en MB
|
| 6 |
max_upload_mb: 500
|
| 7 |
|
| 8 |
+
data_origin: "internal"
|
| 9 |
+
|
| 10 |
api:
|
| 11 |
# URL pública de tu space engine (puedes interpolar la var de entorno)
|
| 12 |
base_url: "${API_BASE_URL}"
|
|
|
|
| 41 |
page_icon: "🎧"
|
| 42 |
wide_layout: true
|
| 43 |
|
| 44 |
+
compliance:
|
| 45 |
+
# Controla la integració amb AWS QLDB (registre privat) i Polygon (registre públic)
|
| 46 |
+
private_blockchain_enable: false
|
| 47 |
+
public_blockchain_enable: false
|
| 48 |
+
monthly_digest_enabled: false
|
| 49 |
|
mobile_verification.py
CHANGED
|
@@ -14,6 +14,8 @@ import streamlit as st
|
|
| 14 |
from datetime import datetime
|
| 15 |
from typing import Optional, Tuple
|
| 16 |
|
|
|
|
|
|
|
| 17 |
|
| 18 |
def log(msg: str):
|
| 19 |
"""Helper per logging amb timestamp"""
|
|
@@ -150,20 +152,48 @@ def render_mobile_verification_screen(username: str, role: str) -> Optional[bool
|
|
| 150 |
key="mobile_accept_terms"
|
| 151 |
)
|
| 152 |
|
| 153 |
-
# Botón para enviar código
|
| 154 |
if st.button(
|
| 155 |
"📤 Enviar codi de verificació",
|
| 156 |
type="primary",
|
| 157 |
disabled=not (phone_number and accept_terms)
|
| 158 |
):
|
| 159 |
full_phone = f"{country_code}{phone_number}"
|
|
|
|
|
|
|
| 160 |
if send_sms_code(full_phone):
|
| 161 |
st.session_state.sms_step = 'verify'
|
| 162 |
st.session_state.sms_phone = full_phone
|
| 163 |
st.success(f"✅ Codi enviat a {full_phone}")
|
| 164 |
-
st.rerun()
|
| 165 |
else:
|
| 166 |
st.error("❌ Error enviant el codi. Torna-ho a intentar.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
# Si se envió el SMS, mostrar campo para verificar código
|
| 169 |
if st.session_state.sms_step == 'verify':
|
|
|
|
| 14 |
from datetime import datetime
|
| 15 |
from typing import Optional, Tuple
|
| 16 |
|
| 17 |
+
from compliance_client import compliance_client
|
| 18 |
+
|
| 19 |
|
| 20 |
def log(msg: str):
|
| 21 |
"""Helper per logging amb timestamp"""
|
|
|
|
| 152 |
key="mobile_accept_terms"
|
| 153 |
)
|
| 154 |
|
| 155 |
+
# Botón para enviar código (SMS demo) i, en paral·lel, registrar sessió per WhatsApp al backend
|
| 156 |
if st.button(
|
| 157 |
"📤 Enviar codi de verificació",
|
| 158 |
type="primary",
|
| 159 |
disabled=not (phone_number and accept_terms)
|
| 160 |
):
|
| 161 |
full_phone = f"{country_code}{phone_number}"
|
| 162 |
+
|
| 163 |
+
# 1) Flux SMS demo local (no canvia)
|
| 164 |
if send_sms_code(full_phone):
|
| 165 |
st.session_state.sms_step = 'verify'
|
| 166 |
st.session_state.sms_phone = full_phone
|
| 167 |
st.success(f"✅ Codi enviat a {full_phone}")
|
|
|
|
| 168 |
else:
|
| 169 |
st.error("❌ Error enviant el codi. Torna-ho a intentar.")
|
| 170 |
+
|
| 171 |
+
# 2) Crear sessió de verificació per WhatsApp al backend de compliance
|
| 172 |
+
try:
|
| 173 |
+
user_obj = st.session_state.get("user") or {}
|
| 174 |
+
user_id = str(user_obj.get("id")) if isinstance(user_obj, dict) and "id" in user_obj else None
|
| 175 |
+
email = None # es pot afegir més endavant si hi ha email disponible
|
| 176 |
+
|
| 177 |
+
resp = compliance_client.create_phone_verification_session(
|
| 178 |
+
page="demo/mobile_verification",
|
| 179 |
+
user_id=user_id,
|
| 180 |
+
email=email,
|
| 181 |
+
action="accept_terms_mobile_verification",
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
if resp and "session_code" in resp:
|
| 185 |
+
session_code = resp["session_code"]
|
| 186 |
+
expires_at = resp.get("expires_at")
|
| 187 |
+
st.info(
|
| 188 |
+
"📱 **Verificació per WhatsApp (registre de firma)**\n\n"
|
| 189 |
+
"Envia un WhatsApp des del teu mòbil al **número oficial del projecte** amb el text:\n\n"
|
| 190 |
+
f"`ACEPTO {session_code}`\n\n"
|
| 191 |
+
"Tens fins a **5 minuts** per enviar-lo."
|
| 192 |
+
)
|
| 193 |
+
else:
|
| 194 |
+
log("[COMPLIANCE] No s'ha pogut crear la sessió de verificació per telèfon")
|
| 195 |
+
except Exception as e:
|
| 196 |
+
log(f"[COMPLIANCE] Error cridant create_phone_verification_session: {e}")
|
| 197 |
|
| 198 |
# Si se envió el SMS, mostrar campo para verificar código
|
| 199 |
if st.session_state.sms_step == 'verify':
|
persistent_data_gate.py
CHANGED
|
@@ -22,6 +22,38 @@ def _load_data_origin(base_dir: Path) -> str:
|
|
| 22 |
return origin
|
| 23 |
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def ensure_temp_databases(base_dir: Path, api_client) -> None:
|
| 26 |
"""Garantiza que las BDs *.db estén presentes en demo/temp antes del login.
|
| 27 |
|
|
@@ -30,6 +62,8 @@ def ensure_temp_databases(base_dir: Path, api_client) -> None:
|
|
| 30 |
"""
|
| 31 |
|
| 32 |
data_origin = _load_data_origin(base_dir)
|
|
|
|
|
|
|
| 33 |
temp_dir = base_dir / "temp"
|
| 34 |
temp_dir.mkdir(parents=True, exist_ok=True)
|
| 35 |
|
|
@@ -198,7 +232,48 @@ def confirm_changes_and_logout(base_dir: Path, api_client, session_id: str) -> N
|
|
| 198 |
except Exception:
|
| 199 |
pass
|
| 200 |
|
| 201 |
-
# --- 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
videos_db = temp_dir / "videos.db"
|
| 203 |
new_sha1s: set[str] = set()
|
| 204 |
|
|
|
|
| 22 |
return origin
|
| 23 |
|
| 24 |
|
| 25 |
+
def _load_compliance_flags(base_dir: Path) -> dict:
|
| 26 |
+
"""Llegeix flags de la secció 'compliance' de demo/config.yaml.
|
| 27 |
+
|
| 28 |
+
Retorna un dict amb claus:
|
| 29 |
+
- private_blockchain_enable
|
| 30 |
+
- public_blockchain_enable
|
| 31 |
+
- monthly_digest_enabled
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
cfg_path = base_dir / "config.yaml"
|
| 35 |
+
flags = {
|
| 36 |
+
"private_blockchain_enable": False,
|
| 37 |
+
"public_blockchain_enable": False,
|
| 38 |
+
"monthly_digest_enabled": False,
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
import yaml
|
| 43 |
+
|
| 44 |
+
with cfg_path.open("r", encoding="utf-8") as f:
|
| 45 |
+
cfg = yaml.safe_load(f) or {}
|
| 46 |
+
comp = cfg.get("compliance", {}) or {}
|
| 47 |
+
flags["private_blockchain_enable"] = bool(comp.get("private_blockchain_enable", False))
|
| 48 |
+
flags["public_blockchain_enable"] = bool(comp.get("public_blockchain_enable", False))
|
| 49 |
+
flags["monthly_digest_enabled"] = bool(comp.get("monthly_digest_enabled", False))
|
| 50 |
+
except Exception:
|
| 51 |
+
# Manté valors per defecte en cas d'error
|
| 52 |
+
return flags
|
| 53 |
+
|
| 54 |
+
return flags
|
| 55 |
+
|
| 56 |
+
|
| 57 |
def ensure_temp_databases(base_dir: Path, api_client) -> None:
|
| 58 |
"""Garantiza que las BDs *.db estén presentes en demo/temp antes del login.
|
| 59 |
|
|
|
|
| 62 |
"""
|
| 63 |
|
| 64 |
data_origin = _load_data_origin(base_dir)
|
| 65 |
+
compliance_flags = _load_compliance_flags(base_dir)
|
| 66 |
+
public_blockchain_enable = bool(compliance_flags.get("public_blockchain_enable", False))
|
| 67 |
temp_dir = base_dir / "temp"
|
| 68 |
temp_dir.mkdir(parents=True, exist_ok=True)
|
| 69 |
|
|
|
|
| 232 |
except Exception:
|
| 233 |
pass
|
| 234 |
|
| 235 |
+
# --- 2) Digest d'esdeveniments per a la sessió (public blockchain) ---
|
| 236 |
+
if public_blockchain_enable:
|
| 237 |
+
events_db = temp_dir / "events.db"
|
| 238 |
+
try:
|
| 239 |
+
import sqlite3
|
| 240 |
+
import hashlib
|
| 241 |
+
import json
|
| 242 |
+
|
| 243 |
+
with sqlite3.connect(str(events_db)) as econn:
|
| 244 |
+
econn.row_factory = sqlite3.Row
|
| 245 |
+
cur = econn.cursor()
|
| 246 |
+
|
| 247 |
+
# Comprovar que existeix la taula events
|
| 248 |
+
cur.execute(
|
| 249 |
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='events'"
|
| 250 |
+
)
|
| 251 |
+
if cur.fetchone():
|
| 252 |
+
cur.execute(
|
| 253 |
+
"SELECT * FROM events WHERE session = ?", (session_id,)
|
| 254 |
+
)
|
| 255 |
+
rows = cur.fetchall()
|
| 256 |
+
if rows:
|
| 257 |
+
payload = []
|
| 258 |
+
for r in rows:
|
| 259 |
+
payload.append({k: r[k] for k in r.keys()})
|
| 260 |
+
|
| 261 |
+
serialized = json.dumps(
|
| 262 |
+
payload, sort_keys=True, separators=(",", ":")
|
| 263 |
+
)
|
| 264 |
+
digest_hash = hashlib.sha256(
|
| 265 |
+
serialized.encode("utf-8")
|
| 266 |
+
).hexdigest()
|
| 267 |
+
# Simular publicació a Polygon: log amb prefix clar
|
| 268 |
+
print(
|
| 269 |
+
f"[POLYGON EVENTS DIGEST] session={session_id} "
|
| 270 |
+
f"events={len(payload)} hash={digest_hash}"
|
| 271 |
+
)
|
| 272 |
+
except Exception:
|
| 273 |
+
# No aturar el procés si hi ha errors en el càlcul del digest
|
| 274 |
+
pass
|
| 275 |
+
|
| 276 |
+
# --- 3) Nous vídeos a videos.db associats a la sessió ---
|
| 277 |
videos_db = temp_dir / "videos.db"
|
| 278 |
new_sha1s: set[str] = set()
|
| 279 |
|