Upload 10 files
Browse files- app.py +9 -12
- compliance_client.py +12 -3
- databases.py +4 -17
- mobile_verification.py +22 -0
- page_modules/machine_room.py +25 -0
- page_modules/new_video_processing.py +48 -11
- page_modules/validation.py +15 -1
- persistent_data_gate.py +145 -14
app.py
CHANGED
|
@@ -14,7 +14,7 @@ from databases import set_db_path, init_schema, set_blockchain_enabled
|
|
| 14 |
from api_client import APIClient
|
| 15 |
from utils import ensure_dirs
|
| 16 |
from auth import initialize_auth_system, render_login_form, render_sidebar, require_login
|
| 17 |
-
from persistent_data_gate import ensure_temp_databases
|
| 18 |
from mobile_verification import render_mobile_verification_screen, get_user_permissions
|
| 19 |
from compliance_client import compliance_client
|
| 20 |
from page_modules.new_video_processing import render_process_video_page
|
|
@@ -117,17 +117,14 @@ set_db_path(DB_PATH)
|
|
| 117 |
# Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB
|
| 118 |
set_blockchain_enabled(PRIVATE_BLOCKCHAIN_ENABLE)
|
| 119 |
|
| 120 |
-
# Si est脿 habilitat el registre p煤blic i el digest mensual, publicar
|
| 121 |
-
#
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
except Exception:
|
| 129 |
-
# No bloquejar l'app per errors de blockchain/compliance
|
| 130 |
-
pass
|
| 131 |
|
| 132 |
init_schema()
|
| 133 |
|
|
|
|
| 14 |
from api_client import APIClient
|
| 15 |
from utils import ensure_dirs
|
| 16 |
from auth import initialize_auth_system, render_login_form, render_sidebar, require_login
|
| 17 |
+
from persistent_data_gate import ensure_temp_databases, maybe_publish_monthly_actions_digest
|
| 18 |
from mobile_verification import render_mobile_verification_screen, get_user_permissions
|
| 19 |
from compliance_client import compliance_client
|
| 20 |
from page_modules.new_video_processing import render_process_video_page
|
|
|
|
| 117 |
# Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB
|
| 118 |
set_blockchain_enabled(PRIVATE_BLOCKCHAIN_ENABLE)
|
| 119 |
|
| 120 |
+
# Si est脿 habilitat el registre p煤blic i el digest mensual, publicar si cal
|
| 121 |
+
# el digest mensual sobre actions.db (es calcula al voltant del mes anterior).
|
| 122 |
+
maybe_publish_monthly_actions_digest(
|
| 123 |
+
base_dir,
|
| 124 |
+
compliance_client,
|
| 125 |
+
public_blockchain_enabled=PUBLIC_BLOCKCHAIN_ENABLE,
|
| 126 |
+
monthly_digest_enabled=MONTHLY_DIGEST_ENABLED,
|
| 127 |
+
)
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
init_schema()
|
| 130 |
|
compliance_client.py
CHANGED
|
@@ -569,9 +569,18 @@ class ComplianceClient:
|
|
| 569 |
|
| 570 |
# === M脡TODOS DE BLOCKCHAIN (POLYGON) ===
|
| 571 |
|
| 572 |
-
def publish_monthly_digest(self, period: str) -> Optional[str]:
|
| 573 |
-
"""Publica digest mensual en blockchain
|
| 574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
|
| 576 |
if response:
|
| 577 |
tx_hash = response.get("transaction_hash")
|
|
|
|
| 569 |
|
| 570 |
# === M脡TODOS DE BLOCKCHAIN (POLYGON) ===
|
| 571 |
|
| 572 |
+
def publish_monthly_digest(self, period: str, digest_hash: str | None = None) -> Optional[str]:
|
| 573 |
+
"""Publica digest mensual en blockchain.
|
| 574 |
+
|
| 575 |
+
Si digest_hash no 茅s None, s'envia tamb茅 al backend perqu猫 el faci
|
| 576 |
+
servir com a arrel del digest mensual.
|
| 577 |
+
"""
|
| 578 |
+
|
| 579 |
+
payload: Dict[str, Any] = {"period": period}
|
| 580 |
+
if digest_hash:
|
| 581 |
+
payload["digest_hash"] = digest_hash
|
| 582 |
+
|
| 583 |
+
response = self._make_request("POST", "/api/blockchain/publish-digest", payload)
|
| 584 |
|
| 585 |
if response:
|
| 586 |
tx_hash = response.get("transaction_hash")
|
databases.py
CHANGED
|
@@ -275,11 +275,11 @@ def get_accessible_videos_with_sha1(session_id: str | None) -> List[Dict[str, An
|
|
| 275 |
# Ordenem per nom de v铆deo per estabilitat
|
| 276 |
return sorted(public_rows.values(), key=lambda r: (r["video_name"] or r["sha1sum"]))
|
| 277 |
|
| 278 |
-
# 2) Tel猫fons associats a la sessi贸 actual
|
| 279 |
phones: set[str] = set()
|
| 280 |
-
with
|
| 281 |
-
for row in
|
| 282 |
-
"SELECT DISTINCT phone FROM
|
| 283 |
(session_id,),
|
| 284 |
):
|
| 285 |
phones.add(row["phone"])
|
|
@@ -930,19 +930,6 @@ def insert_demo_feedback_row(
|
|
| 930 |
)
|
| 931 |
|
| 932 |
|
| 933 |
-
def _connect_events_db() -> sqlite3.Connection:
|
| 934 |
-
"""Connexi贸 directa a demo/temp/events.db.
|
| 935 |
-
|
| 936 |
-
Es fa independent de DEFAULT_DB_PATH per mantenir aquesta BD separada
|
| 937 |
-
de users.db, igual que feedback.db.
|
| 938 |
-
"""
|
| 939 |
-
|
| 940 |
-
EVENTS_DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 941 |
-
conn = sqlite3.connect(str(EVENTS_DB_PATH))
|
| 942 |
-
conn.row_factory = sqlite3.Row
|
| 943 |
-
return conn
|
| 944 |
-
|
| 945 |
-
|
| 946 |
def _connect_videos_db() -> sqlite3.Connection:
|
| 947 |
"""Connexi贸 directa a demo/temp/videos.db.
|
| 948 |
|
|
|
|
| 275 |
# Ordenem per nom de v铆deo per estabilitat
|
| 276 |
return sorted(public_rows.values(), key=lambda r: (r["video_name"] or r["sha1sum"]))
|
| 277 |
|
| 278 |
+
# 2) Tel猫fons associats a la sessi贸 actual (a partir de actions.db)
|
| 279 |
phones: set[str] = set()
|
| 280 |
+
with _connect_actions_db() as aconn:
|
| 281 |
+
for row in aconn.execute(
|
| 282 |
+
"SELECT DISTINCT phone FROM actions WHERE session = ? AND phone IS NOT NULL AND phone != ''",
|
| 283 |
(session_id,),
|
| 284 |
):
|
| 285 |
phones.add(row["phone"])
|
|
|
|
| 930 |
)
|
| 931 |
|
| 932 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 933 |
def _connect_videos_db() -> sqlite3.Connection:
|
| 934 |
"""Connexi贸 directa a demo/temp/videos.db.
|
| 935 |
|
mobile_verification.py
CHANGED
|
@@ -108,6 +108,28 @@ def send_sms_code(phone_number: str) -> bool:
|
|
| 108 |
log("[SMS] Error retornat per compliance en enviar l'SMS de login")
|
| 109 |
return False
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
return True
|
| 112 |
except Exception as e:
|
| 113 |
log(f"[SMS] Error generant/enviant codi via compliance: {e}")
|
|
|
|
| 108 |
log("[SMS] Error retornat per compliance en enviar l'SMS de login")
|
| 109 |
return False
|
| 110 |
|
| 111 |
+
# Registrar SMS de login a actions.db
|
| 112 |
+
try:
|
| 113 |
+
from databases import log_action
|
| 114 |
+
|
| 115 |
+
session_id = st.session_state.get("session_id", "")
|
| 116 |
+
user_obj = st.session_state.get("user") or {}
|
| 117 |
+
username = (
|
| 118 |
+
user_obj.get("username")
|
| 119 |
+
if isinstance(user_obj, dict)
|
| 120 |
+
else str(user_obj or "")
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
log_action(
|
| 124 |
+
session=session_id,
|
| 125 |
+
user=username,
|
| 126 |
+
phone=normalized_phone,
|
| 127 |
+
action="SMS sent for login verification",
|
| 128 |
+
sha1sum="",
|
| 129 |
+
)
|
| 130 |
+
except Exception:
|
| 131 |
+
pass
|
| 132 |
+
|
| 133 |
return True
|
| 134 |
except Exception as e:
|
| 135 |
log(f"[SMS] Error generant/enviant codi via compliance: {e}")
|
page_modules/machine_room.py
CHANGED
|
@@ -182,3 +182,28 @@ def render_machine_room_page() -> None:
|
|
| 182 |
_save_config(cfg)
|
| 183 |
st.success("Configuraci贸 desada correctament. Reinicia la p脿gina per veure els canvis aplicats.")
|
| 184 |
st.experimental_rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
_save_config(cfg)
|
| 183 |
st.success("Configuraci贸 desada correctament. Reinicia la p脿gina per veure els canvis aplicats.")
|
| 184 |
st.experimental_rerun()
|
| 185 |
+
|
| 186 |
+
# Botons d'accions avan莽ades a la part inferior de la pantalla
|
| 187 |
+
st.markdown("---")
|
| 188 |
+
|
| 189 |
+
# Estil b脿sic per fer els botons secundaris de color taronja
|
| 190 |
+
st.markdown(
|
| 191 |
+
"""
|
| 192 |
+
<style>
|
| 193 |
+
div[data-testid="baseButton-secondary"] button {
|
| 194 |
+
background-color: #f97316 !important;
|
| 195 |
+
border-color: #ea580c !important;
|
| 196 |
+
color: white !important;
|
| 197 |
+
}
|
| 198 |
+
</style>
|
| 199 |
+
""",
|
| 200 |
+
unsafe_allow_html=True,
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
col_b1, col_b2, col_b3 = st.columns(3)
|
| 204 |
+
with col_b1:
|
| 205 |
+
st.button("Bu茂dar fitxers temporals", type="secondary", use_container_width=True)
|
| 206 |
+
with col_b2:
|
| 207 |
+
st.button("Reconstruir audiodescripcions", type="secondary", use_container_width=True)
|
| 208 |
+
with col_b3:
|
| 209 |
+
st.button("Reentrenar refinament", type="secondary", use_container_width=True)
|
page_modules/new_video_processing.py
CHANGED
|
@@ -492,10 +492,35 @@ def render_process_video_page(api, backend_base_url: str) -> None:
|
|
| 492 |
# Notificar al validador per SMS nom茅s si est脿 habilitat a config.yaml
|
| 493 |
if video_validator_sms_enabled:
|
| 494 |
try:
|
| 495 |
-
compliance_client.notify_video_upload(
|
| 496 |
video_name=uploaded_file.name,
|
| 497 |
sha1sum=sha1,
|
| 498 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
except Exception as sms_exc:
|
| 500 |
print(f"[VIDEO SMS] Error enviant notificaci贸 al validor: {sms_exc}")
|
| 501 |
else:
|
|
@@ -1814,27 +1839,39 @@ def render_process_video_page(api, backend_base_url: str) -> None:
|
|
| 1814 |
try:
|
| 1815 |
# Text de l'SMS en catal脿, tal com has indicat
|
| 1816 |
sms_msg = "Noves audiodescripcions a validar segons la norma UNE-153020"
|
| 1817 |
-
compliance_client.notify_une_validator_new_ads(
|
| 1818 |
phone=une_phone_validator,
|
| 1819 |
message=sms_msg,
|
| 1820 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1821 |
except Exception as e_sms_call:
|
| 1822 |
_log(f"[UNE SMS] Error cridant compliance per UNE: {e_sms_call}")
|
| 1823 |
|
| 1824 |
-
# Registrar estat d'espera de validaci贸 UNE a
|
| 1825 |
try:
|
| 1826 |
-
|
| 1827 |
-
|
| 1828 |
-
|
|
|
|
| 1829 |
user=username or "",
|
| 1830 |
-
|
| 1831 |
-
phone=une_phone_validator,
|
| 1832 |
action="Waiting for UNE validation",
|
| 1833 |
-
sha1sum=sha1,
|
| 1834 |
-
visibility=vis_flag,
|
| 1835 |
)
|
| 1836 |
except Exception as e_evt_wait:
|
| 1837 |
-
_log(f"[
|
| 1838 |
except Exception as e_sms:
|
| 1839 |
_log(f"[UNE SMS] Error en flux d'SMS/espera validaci贸: {e_sms}")
|
| 1840 |
|
|
|
|
| 492 |
# Notificar al validador per SMS nom茅s si est脿 habilitat a config.yaml
|
| 493 |
if video_validator_sms_enabled:
|
| 494 |
try:
|
| 495 |
+
sms_ok = compliance_client.notify_video_upload(
|
| 496 |
video_name=uploaded_file.name,
|
| 497 |
sha1sum=sha1,
|
| 498 |
)
|
| 499 |
+
if sms_ok:
|
| 500 |
+
try:
|
| 501 |
+
from databases import log_action
|
| 502 |
+
|
| 503 |
+
session_id = st.session_state.get("session_id", "")
|
| 504 |
+
user_obj = st.session_state.get("user") or {}
|
| 505 |
+
username = (
|
| 506 |
+
user_obj.get("username")
|
| 507 |
+
if isinstance(user_obj, dict)
|
| 508 |
+
else str(user_obj or "")
|
| 509 |
+
)
|
| 510 |
+
phone = (
|
| 511 |
+
st.session_state.get("sms_phone_verified")
|
| 512 |
+
or st.session_state.get("sms_phone")
|
| 513 |
+
or ""
|
| 514 |
+
)
|
| 515 |
+
log_action(
|
| 516 |
+
session=session_id,
|
| 517 |
+
user=username,
|
| 518 |
+
phone=phone,
|
| 519 |
+
action="SMS sent to video validator",
|
| 520 |
+
sha1sum=sha1 or "",
|
| 521 |
+
)
|
| 522 |
+
except Exception:
|
| 523 |
+
pass
|
| 524 |
except Exception as sms_exc:
|
| 525 |
print(f"[VIDEO SMS] Error enviant notificaci贸 al validor: {sms_exc}")
|
| 526 |
else:
|
|
|
|
| 1839 |
try:
|
| 1840 |
# Text de l'SMS en catal脿, tal com has indicat
|
| 1841 |
sms_msg = "Noves audiodescripcions a validar segons la norma UNE-153020"
|
| 1842 |
+
sms_ok = compliance_client.notify_une_validator_new_ads(
|
| 1843 |
phone=une_phone_validator,
|
| 1844 |
message=sms_msg,
|
| 1845 |
)
|
| 1846 |
+
if sms_ok:
|
| 1847 |
+
try:
|
| 1848 |
+
from databases import log_action
|
| 1849 |
+
|
| 1850 |
+
log_action(
|
| 1851 |
+
session=session_id or "",
|
| 1852 |
+
user=username or "",
|
| 1853 |
+
phone=str(une_phone_validator or ""),
|
| 1854 |
+
action="SMS sent to UNE validator",
|
| 1855 |
+
sha1sum=sha1 or "",
|
| 1856 |
+
)
|
| 1857 |
+
except Exception:
|
| 1858 |
+
pass
|
| 1859 |
except Exception as e_sms_call:
|
| 1860 |
_log(f"[UNE SMS] Error cridant compliance per UNE: {e_sms_call}")
|
| 1861 |
|
| 1862 |
+
# Registrar estat d'espera de validaci贸 UNE a actions.db
|
| 1863 |
try:
|
| 1864 |
+
from databases import log_action
|
| 1865 |
+
|
| 1866 |
+
log_action(
|
| 1867 |
+
session=session_id or "",
|
| 1868 |
user=username or "",
|
| 1869 |
+
phone=str(une_phone_validator or ""),
|
|
|
|
| 1870 |
action="Waiting for UNE validation",
|
| 1871 |
+
sha1sum=sha1 or "",
|
|
|
|
| 1872 |
)
|
| 1873 |
except Exception as e_evt_wait:
|
| 1874 |
+
_log(f"[actions] Error registrant Waiting for UNE validation: {e_evt_wait}")
|
| 1875 |
except Exception as e_sms:
|
| 1876 |
_log(f"[UNE SMS] Error en flux d'SMS/espera validaci贸: {e_sms}")
|
| 1877 |
|
page_modules/validation.py
CHANGED
|
@@ -278,11 +278,25 @@ def render_validation_page(
|
|
| 278 |
try:
|
| 279 |
# Text proporcionat (en castell脿, per貌 segons els requisits de l'SMS)
|
| 280 |
msg = "Su v铆deo ha sido aprobado. Puede entrar en la aplicaci贸n y subirlo de nuevo para generar la audiodescripci贸n"
|
| 281 |
-
compliance_client.notify_user_video_approved(
|
| 282 |
phone=owner_phone,
|
| 283 |
message=msg,
|
| 284 |
sha1sum=sha1,
|
| 285 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
except Exception as e_sms:
|
| 287 |
_log(f"[VIDEO USER SMS] Error enviant SMS a l'usuari: {e_sms}")
|
| 288 |
|
|
|
|
| 278 |
try:
|
| 279 |
# Text proporcionat (en castell脿, per貌 segons els requisits de l'SMS)
|
| 280 |
msg = "Su v铆deo ha sido aprobado. Puede entrar en la aplicaci贸n y subirlo de nuevo para generar la audiodescripci贸n"
|
| 281 |
+
sms_ok = compliance_client.notify_user_video_approved(
|
| 282 |
phone=owner_phone,
|
| 283 |
message=msg,
|
| 284 |
sha1sum=sha1,
|
| 285 |
)
|
| 286 |
+
if sms_ok:
|
| 287 |
+
try:
|
| 288 |
+
from databases import log_action
|
| 289 |
+
|
| 290 |
+
session_id = st.session_state.get("session_id", "")
|
| 291 |
+
log_action(
|
| 292 |
+
session=session_id,
|
| 293 |
+
user=username or "",
|
| 294 |
+
phone=owner_phone,
|
| 295 |
+
action="SMS sent to user for video approval",
|
| 296 |
+
sha1sum=sha1 or "",
|
| 297 |
+
)
|
| 298 |
+
except Exception:
|
| 299 |
+
pass
|
| 300 |
except Exception as e_sms:
|
| 301 |
_log(f"[VIDEO USER SMS] Error enviant SMS a l'usuari: {e_sms}")
|
| 302 |
|
persistent_data_gate.py
CHANGED
|
@@ -109,7 +109,7 @@ def ensure_temp_databases(base_dir: Path, api_client) -> None:
|
|
| 109 |
else:
|
| 110 |
# Mode external: descarregar BDs del backend una sola vegada per sessi贸 del servidor
|
| 111 |
ext_marker = db_temp_dir / ".external_db_imported"
|
| 112 |
-
missing = [name for name in ("
|
| 113 |
|
| 114 |
if ext_marker.exists() and not missing:
|
| 115 |
return
|
|
@@ -136,6 +136,115 @@ def ensure_temp_databases(base_dir: Path, api_client) -> None:
|
|
| 136 |
return
|
| 137 |
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
def _extract_zip_bytes(zip_bytes: bytes, target_dir: Path) -> None:
|
| 140 |
target_dir.mkdir(parents=True, exist_ok=True)
|
| 141 |
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
|
@@ -349,26 +458,26 @@ def confirm_changes_and_logout(base_dir: Path, api_client, session_id: str) -> N
|
|
| 349 |
# No aturar tot el proc茅s per un error puntual
|
| 350 |
continue
|
| 351 |
|
| 352 |
-
# --- 2) Digest d'
|
| 353 |
events_digest_info = None
|
| 354 |
if public_blockchain_enabled:
|
| 355 |
-
|
| 356 |
try:
|
| 357 |
import sqlite3
|
| 358 |
import hashlib
|
| 359 |
import json
|
| 360 |
|
| 361 |
-
with sqlite3.connect(str(
|
| 362 |
-
|
| 363 |
-
cur =
|
| 364 |
|
| 365 |
-
# Comprovar que existeix la taula
|
| 366 |
cur.execute(
|
| 367 |
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='
|
| 368 |
)
|
| 369 |
if cur.fetchone():
|
| 370 |
cur.execute(
|
| 371 |
-
"SELECT * FROM
|
| 372 |
)
|
| 373 |
rows = cur.fetchall()
|
| 374 |
if rows:
|
|
@@ -399,6 +508,19 @@ def confirm_changes_and_logout(base_dir: Path, api_client, session_id: str) -> N
|
|
| 399 |
print(
|
| 400 |
f"[POLYGON PUBLISH] ok tx_hash={tx_hash} tx_url={tx_url}"
|
| 401 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
else:
|
| 403 |
print("[POLYGON PUBLISH] error: resposta buida o nul路la")
|
| 404 |
except Exception as bexc:
|
|
@@ -549,6 +671,8 @@ def confirm_changes_and_logout(base_dir: Path, api_client, session_id: str) -> N
|
|
| 549 |
user_sms_enabled = bool(validation_cfg.get("user_sms_enabled", False))
|
| 550 |
|
| 551 |
if user_sms_enabled:
|
|
|
|
|
|
|
| 552 |
for sha1, phone in revoked_sha1s.items():
|
| 553 |
if not phone:
|
| 554 |
continue
|
|
@@ -557,20 +681,27 @@ def confirm_changes_and_logout(base_dir: Path, api_client, session_id: str) -> N
|
|
| 557 |
"Els permisos per utilitzar el vostre v铆deo han estat revocats. "
|
| 558 |
"Les dades associades han estat eliminades del sistema."
|
| 559 |
)
|
| 560 |
-
compliance_client.notify_user_video_approved(
|
| 561 |
phone=phone,
|
| 562 |
message=msg,
|
| 563 |
sha1sum=sha1,
|
| 564 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
except Exception:
|
| 566 |
continue
|
| 567 |
except Exception:
|
| 568 |
pass
|
| 569 |
|
| 570 |
-
# --- 4) Nous v铆deos a videos.db associats a la sessi贸 (excloent revocats) ---
|
| 571 |
-
videos_db = db_temp_dir / "videos.db"
|
| 572 |
-
new_sha1s: set[str] = set()
|
| 573 |
-
|
| 574 |
try:
|
| 575 |
import sqlite3
|
| 576 |
|
|
|
|
| 109 |
else:
|
| 110 |
# Mode external: descarregar BDs del backend una sola vegada per sessi贸 del servidor
|
| 111 |
ext_marker = db_temp_dir / ".external_db_imported"
|
| 112 |
+
missing = [name for name in ("feedback.db", "users.db", "videos.db") if not (db_temp_dir / name).exists()]
|
| 113 |
|
| 114 |
if ext_marker.exists() and not missing:
|
| 115 |
return
|
|
|
|
| 136 |
return
|
| 137 |
|
| 138 |
|
| 139 |
+
def maybe_publish_monthly_actions_digest(
|
| 140 |
+
base_dir: Path,
|
| 141 |
+
compliance_client,
|
| 142 |
+
public_blockchain_enabled: bool,
|
| 143 |
+
monthly_digest_enabled: bool,
|
| 144 |
+
) -> None:
|
| 145 |
+
"""Publica, si cal, el digest mensual sobre actions.db a Polygon.
|
| 146 |
+
|
| 147 |
+
S'executa despr茅s de tenir demo/temp/db poblats. Calcula el hash de totes
|
| 148 |
+
les accions del mes anterior (segons camp timestamp) i, si encara no s'ha
|
| 149 |
+
registrat una acci贸 "Monthly Digest sent to Polygon" amb aquest hash,
|
| 150 |
+
el publica via servei de compliance.
|
| 151 |
+
"""
|
| 152 |
+
|
| 153 |
+
if not (public_blockchain_enabled and monthly_digest_enabled):
|
| 154 |
+
return
|
| 155 |
+
|
| 156 |
+
try:
|
| 157 |
+
from datetime import datetime, date
|
| 158 |
+
import sqlite3
|
| 159 |
+
import hashlib
|
| 160 |
+
import json
|
| 161 |
+
|
| 162 |
+
db_temp_dir = base_dir / "temp" / "db"
|
| 163 |
+
actions_db_path = db_temp_dir / "actions.db"
|
| 164 |
+
if not actions_db_path.exists():
|
| 165 |
+
return
|
| 166 |
+
|
| 167 |
+
today = datetime.utcnow().date()
|
| 168 |
+
# Per铆ode objectiu: mes anterior
|
| 169 |
+
if today.month == 1:
|
| 170 |
+
year = today.year - 1
|
| 171 |
+
month = 12
|
| 172 |
+
else:
|
| 173 |
+
year = today.year
|
| 174 |
+
month = today.month - 1
|
| 175 |
+
|
| 176 |
+
period = f"{year:04d}-{month:02d}"
|
| 177 |
+
|
| 178 |
+
# Rang de timestamps per al mes anterior
|
| 179 |
+
start_ts = f"{period}-01 00:00:00"
|
| 180 |
+
if month == 12:
|
| 181 |
+
next_year = year + 1
|
| 182 |
+
next_month = 1
|
| 183 |
+
else:
|
| 184 |
+
next_year = year
|
| 185 |
+
next_month = month + 1
|
| 186 |
+
end_period = f"{next_year:04d}-{next_month:02d}"
|
| 187 |
+
end_ts = f"{end_period}-01 00:00:00"
|
| 188 |
+
|
| 189 |
+
with sqlite3.connect(str(actions_db_path)) as conn:
|
| 190 |
+
conn.row_factory = sqlite3.Row
|
| 191 |
+
cur = conn.cursor()
|
| 192 |
+
|
| 193 |
+
# Assegurar que la taula actions existeix
|
| 194 |
+
cur.execute(
|
| 195 |
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='actions'"
|
| 196 |
+
)
|
| 197 |
+
if not cur.fetchone():
|
| 198 |
+
return
|
| 199 |
+
|
| 200 |
+
# Llegir totes les accions del per铆ode
|
| 201 |
+
cur.execute(
|
| 202 |
+
"SELECT * FROM actions WHERE timestamp >= ? AND timestamp < ?",
|
| 203 |
+
(start_ts, end_ts),
|
| 204 |
+
)
|
| 205 |
+
rows = cur.fetchall()
|
| 206 |
+
if not rows:
|
| 207 |
+
return
|
| 208 |
+
|
| 209 |
+
payload: list[dict[str, Any]] = []
|
| 210 |
+
for r in rows:
|
| 211 |
+
payload.append({k: r[k] for k in r.keys()})
|
| 212 |
+
|
| 213 |
+
serialized = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
| 214 |
+
digest_hash = hashlib.sha256(serialized.encode("utf-8")).hexdigest()
|
| 215 |
+
|
| 216 |
+
# Comprovar si ja hem enregistrat aquest digest com enviat
|
| 217 |
+
cur.execute(
|
| 218 |
+
"SELECT 1 FROM actions WHERE action = 'Monthly Digest sent to Polygon' AND sha1sum = ? LIMIT 1",
|
| 219 |
+
(digest_hash,),
|
| 220 |
+
)
|
| 221 |
+
if cur.fetchone():
|
| 222 |
+
return
|
| 223 |
+
|
| 224 |
+
# Publicar via servei de compliance
|
| 225 |
+
try:
|
| 226 |
+
resp = compliance_client.publish_monthly_digest(period, digest_hash=digest_hash)
|
| 227 |
+
if resp:
|
| 228 |
+
try:
|
| 229 |
+
from databases import log_action
|
| 230 |
+
|
| 231 |
+
log_action(
|
| 232 |
+
session="", # digest global per per铆ode
|
| 233 |
+
user="",
|
| 234 |
+
phone="",
|
| 235 |
+
action="Monthly Digest sent to Polygon",
|
| 236 |
+
sha1sum=digest_hash,
|
| 237 |
+
)
|
| 238 |
+
except Exception:
|
| 239 |
+
pass
|
| 240 |
+
except Exception:
|
| 241 |
+
# No aturar el flux per errors de digest mensual
|
| 242 |
+
return
|
| 243 |
+
except Exception:
|
| 244 |
+
# No ha de bloquejar l'aplicaci贸
|
| 245 |
+
return
|
| 246 |
+
|
| 247 |
+
|
| 248 |
def _extract_zip_bytes(zip_bytes: bytes, target_dir: Path) -> None:
|
| 249 |
target_dir.mkdir(parents=True, exist_ok=True)
|
| 250 |
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
|
|
|
| 458 |
# No aturar tot el proc茅s per un error puntual
|
| 459 |
continue
|
| 460 |
|
| 461 |
+
# --- 2) Digest d'accions per a la sessi贸 (public blockchain) ---
|
| 462 |
events_digest_info = None
|
| 463 |
if public_blockchain_enabled:
|
| 464 |
+
actions_db_path = db_temp_dir / "actions.db"
|
| 465 |
try:
|
| 466 |
import sqlite3
|
| 467 |
import hashlib
|
| 468 |
import json
|
| 469 |
|
| 470 |
+
with sqlite3.connect(str(actions_db_path)) as aconn:
|
| 471 |
+
aconn.row_factory = sqlite3.Row
|
| 472 |
+
cur = aconn.cursor()
|
| 473 |
|
| 474 |
+
# Comprovar que existeix la taula actions
|
| 475 |
cur.execute(
|
| 476 |
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='actions'"
|
| 477 |
)
|
| 478 |
if cur.fetchone():
|
| 479 |
cur.execute(
|
| 480 |
+
"SELECT * FROM actions WHERE session = ?", (session_id,)
|
| 481 |
)
|
| 482 |
rows = cur.fetchall()
|
| 483 |
if rows:
|
|
|
|
| 508 |
print(
|
| 509 |
f"[POLYGON PUBLISH] ok tx_hash={tx_hash} tx_url={tx_url}"
|
| 510 |
)
|
| 511 |
+
# Registrar publicaci贸 del digest a actions.db
|
| 512 |
+
try:
|
| 513 |
+
from databases import log_action
|
| 514 |
+
|
| 515 |
+
log_action(
|
| 516 |
+
session=session_id or "",
|
| 517 |
+
user="", # no tenim usuari expl铆cit aqu铆
|
| 518 |
+
phone="",
|
| 519 |
+
action="Polygon events digest published",
|
| 520 |
+
sha1sum=digest_hash,
|
| 521 |
+
)
|
| 522 |
+
except Exception:
|
| 523 |
+
pass
|
| 524 |
else:
|
| 525 |
print("[POLYGON PUBLISH] error: resposta buida o nul路la")
|
| 526 |
except Exception as bexc:
|
|
|
|
| 671 |
user_sms_enabled = bool(validation_cfg.get("user_sms_enabled", False))
|
| 672 |
|
| 673 |
if user_sms_enabled:
|
| 674 |
+
from databases import log_action
|
| 675 |
+
|
| 676 |
for sha1, phone in revoked_sha1s.items():
|
| 677 |
if not phone:
|
| 678 |
continue
|
|
|
|
| 681 |
"Els permisos per utilitzar el vostre v铆deo han estat revocats. "
|
| 682 |
"Les dades associades han estat eliminades del sistema."
|
| 683 |
)
|
| 684 |
+
sms_ok = compliance_client.notify_user_video_approved(
|
| 685 |
phone=phone,
|
| 686 |
message=msg,
|
| 687 |
sha1sum=sha1,
|
| 688 |
)
|
| 689 |
+
if sms_ok:
|
| 690 |
+
try:
|
| 691 |
+
log_action(
|
| 692 |
+
session=session_id or "",
|
| 693 |
+
user="", # desconegut en aquest context
|
| 694 |
+
phone=phone,
|
| 695 |
+
action="SMS sent to user for video revocation",
|
| 696 |
+
sha1sum=sha1 or "",
|
| 697 |
+
)
|
| 698 |
+
except Exception:
|
| 699 |
+
pass
|
| 700 |
except Exception:
|
| 701 |
continue
|
| 702 |
except Exception:
|
| 703 |
pass
|
| 704 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
try:
|
| 706 |
import sqlite3
|
| 707 |
|