feat(5.20): US-001/002/003 schema medii per cont + disponibilitate + idempotenta env-aware
US-001: coloane accounts (rar_test/prod_enabled, rar_creds_test/prod_enc, rar_env_default) + submissions.rar_env; migrare cu backfill din ancora globala AUTOPASS_RAR_ENV (creds->slot, enabled doar pe mediul cu creds) + recompute idempotency_key env-aware (AUTO-FIX G + E4/3). US-002: app/rar_env.py — medii_disponibile + rar_env_efectiv (REQ-DISP/DEFAULT). US-003: build_key(account_id, canon, rar_env) — test vs prod = trimiteri distincte. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
app/rar_env.py
Normal file
91
app/rar_env.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Medii RAR per cont (PRD 5.20): disponibilitate + default efectiv.
|
||||
|
||||
Sursa UNICA de adevar pentru REQ-DISP / REQ-DEFAULT: vizibilitatea selector/toggle
|
||||
in UI, validarea tintei in API si decizia worker-ului citesc TOATE de aici, ca sa
|
||||
decida identic.
|
||||
|
||||
Un mediu ('test'|'prod') e *disponibil* pentru un cont daca e activat (bifa) SI are
|
||||
credentiale (slot per-mediu non-gol). Din disponibilitate decurge tot UX-ul:
|
||||
- 0 medii -> nicio tinta; trimiterea web e blocata, API cade pe ancora globala.
|
||||
- 1 mediu -> tinta implicita (acel mediu), fara selector.
|
||||
- 2 medii -> selector la import + toggle in statusbar + alegere in API.
|
||||
|
||||
Functii PURE (fara DB) peste un rand de cont (sqlite3.Row sau dict). Helperele cu
|
||||
`conn` incarca randul si deleaga.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from typing import Any
|
||||
|
||||
VALID_ENVS: tuple[str, str] = ("test", "prod")
|
||||
|
||||
|
||||
def _field(account: Any, key: str, default: Any = None) -> Any:
|
||||
"""Citire toleranta a unui camp de cont (dict sau sqlite3.Row, camp posibil absent)."""
|
||||
if account is None:
|
||||
return default
|
||||
if isinstance(account, dict):
|
||||
return account.get(key, default)
|
||||
try:
|
||||
return account[key] # sqlite3.Row
|
||||
except (IndexError, KeyError):
|
||||
return default
|
||||
|
||||
|
||||
def _are_creds(account: Any, env: str) -> bool:
|
||||
creds = _field(account, f"rar_creds_{env}_enc", None)
|
||||
return bool(creds and str(creds).strip())
|
||||
|
||||
|
||||
def _enabled(account: Any, env: str) -> bool:
|
||||
return int(_field(account, f"rar_{env}_enabled", 0) or 0) == 1
|
||||
|
||||
|
||||
def medii_disponibile(account: Any) -> list[str]:
|
||||
"""Subset din ('test','prod') = activat AND creds prezente. Ordine stabila test<prod."""
|
||||
return [env for env in VALID_ENVS if _enabled(account, env) and _are_creds(account, env)]
|
||||
|
||||
|
||||
def rar_env_efectiv(account: Any) -> str | None:
|
||||
"""Mediul tinta implicit al contului (REQ-DEFAULT).
|
||||
|
||||
Mereu unul din mediile disponibile: default-ul contului daca inca e disponibil,
|
||||
altfel singurul disponibil; daca 0 disponibile -> None (nicio tinta).
|
||||
"""
|
||||
disp = medii_disponibile(account)
|
||||
if not disp:
|
||||
return None
|
||||
default = _field(account, "rar_env_default", "prod")
|
||||
if default in disp:
|
||||
return default
|
||||
return disp[0]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Helpere cu conexiune #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
_ACCOUNT_ENV_COLS = (
|
||||
"id, rar_test_enabled, rar_prod_enabled, "
|
||||
"rar_creds_test_enc, rar_creds_prod_enc, rar_env_default"
|
||||
)
|
||||
|
||||
|
||||
def load_account_env(conn: sqlite3.Connection, account_id: int) -> sqlite3.Row | None:
|
||||
"""Randul de cont cu exact coloanele de mediu (pentru medii_disponibile/rar_env_efectiv)."""
|
||||
from .mapping import account_or_default
|
||||
|
||||
return conn.execute(
|
||||
f"SELECT {_ACCOUNT_ENV_COLS} FROM accounts WHERE id=?",
|
||||
(account_or_default(account_id),),
|
||||
).fetchone()
|
||||
|
||||
|
||||
def medii_disponibile_cont(conn: sqlite3.Connection, account_id: int) -> list[str]:
|
||||
return medii_disponibile(load_account_env(conn, account_id))
|
||||
|
||||
|
||||
def rar_env_efectiv_cont(conn: sqlite3.Connection, account_id: int) -> str | None:
|
||||
return rar_env_efectiv(load_account_env(conn, account_id))
|
||||
Reference in New Issue
Block a user