"""Lifecycle conturi ROAAUTO (admin, fara suprafata HTTP). Functii pure de creare/listare/(de)activare cont, partajate intre CLI (`tools/account.py`, Etapa 3.1) si fluxul web de self-onboarding (Etapa 3.3, care reuseaza `create_account` + `active`). Identitatea de login (email/parola) NU traieste aici — apartine 3.3. NOTA lifecycle `active`: coloana `accounts.active` este un flag de lifecycle consumat de 3.3 (gate „cont in asteptare", `active=0`). Pana la gate-ul worker din 3.3, `active=0` NU opreste trimiterile (worker-ul nu citeste contul, doar `api_keys.active`). `deactivate` marcheaza intentia administrativa; nu blocheaza inca fluxul de trimitere. (Addendum A2.) """ from __future__ import annotations import sqlite3 def _norm_cui(cui: str | None) -> str | None: """trim + upper; sir gol -> None (tratat ca „fara CUI").""" if cui is None: return None cui = cui.strip().upper() return cui or None def create_account( conn: sqlite3.Connection, name: str, cui: str | None = None, active: bool = True ) -> int: """Insereaza un cont si intoarce id-ul nou (AUTOINCREMENT, deci >=2 — nu atinge default id=1). `name` gol/whitespace -> ValueError. `cui` se normalizeaza (trim+upper); un CUI deja folosit -> ValueError cu cauza+fix. Unicitatea e impusa de indexul partial `ux_accounts_cui` (nu de un check separat), deci e sigura la concurenta. """ name = (name or "").strip() if not name: raise ValueError("name gol (un cont are nevoie de nume)") cui = _norm_cui(cui) try: cur = conn.execute( "INSERT INTO accounts (name, cui, active) VALUES (?, ?, ?)", (name, cui, 1 if active else 0), ) except sqlite3.IntegrityError: existing = conn.execute("SELECT id FROM accounts WHERE cui=?", (cui,)).fetchone() owner = existing["id"] if existing else "?" raise ValueError( f"CUI {cui} e deja folosit de contul {owner} " f"(foloseste 'activate --account {owner}' sau alt CUI)" ) return int(cur.lastrowid or 0) def set_active(conn: sqlite3.Connection, account_id: int, active: bool) -> None: """Comuta `accounts.active`. Idempotent (set activ pe activ nu arunca). Cont inexistent -> ValueError.""" row = conn.execute("SELECT 1 FROM accounts WHERE id=?", (account_id,)).fetchone() if not row: raise ValueError(f"cont inexistent: {account_id}") conn.execute("UPDATE accounts SET active=? WHERE id=?", (1 if active else 0, account_id)) def list_accounts(conn: sqlite3.Connection) -> list[dict]: """Metadate conturi (FARA `rar_creds_enc`), ordonate dupa id.""" rows = conn.execute( "SELECT id, name, cui, active, created_at FROM accounts ORDER BY id" ).fetchall() return [dict(r) for r in rows]