feat(5.20): US-013 retragere accounts.rar_creds_enc -> per-env + DROP cu garda
Toate citirile pe coloana legacy accounts.rar_creds_enc mutate pe sloturile per-env (rar_creds_test_enc/rar_creds_prod_enc): worker fallback+keepalive, are_creds (web) si are_creds_rar (integrare, +are_creds_test/_prod), write-back API la reactivare, purjare la stergere cont, _get_acasa_context/_fetch_cont_env_state. Contract API (aditiv): POST /v1/conturi/rar-creds primeste rar_target optional (test/prod), scrie in slotul corect + activeaza mediul; DELETE primeste ?env (sterge un slot sau ambele). Documentat in docs/api-rar-contract.md. DROP cu garda in db.py (schema.sql fara coloana pe DB fresh): - 6a: eliminat ADD COLUMN rar_creds_enc (fara ping-pong re-ADD dupa DROP) - 6b: try/except fail-safe (nu crapa boot-ul) + garda sqlite_version >= 3.35 - 6c: re-backfill old->new imediat inainte de assert (ancora globala) - garda orfane: DROP anulat daca vreun creds legacy nu a aterizat in slot per-env - backup criptat accounts_rar_creds_enc_backup inainte de DROP - 6d: verificare prin PRAGMA table_info (NU grep — submissions are aceeasi coloana) Garda one-way, idempotenta la boot repetat (verificat). submissions.rar_creds_enc ramane neatinsa. tests/test_retragere_creds_enc.py: niciun read pe coloana veche, conturi rar-creds env-aware, are_creds per-env, DROP blocat de garda la lipsa copiere. 9 teste existente actualizate pe sloturi per-env. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
99
app/db.py
99
app/db.py
@@ -86,11 +86,12 @@ def _migrate(conn: sqlite3.Connection) -> None:
|
||||
|
||||
# Coloane accounts
|
||||
acc_cols = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()}
|
||||
if "rar_creds_enc" not in acc_cols:
|
||||
conn.execute("ALTER TABLE accounts ADD COLUMN rar_creds_enc TEXT")
|
||||
acc_cols.add("rar_creds_enc")
|
||||
# AUTO-FIX 6a ELIMINAT (US-013): NU mai adaugam accounts.rar_creds_enc — coloana e dropata.
|
||||
# _migrate_accounts_medii gestioneaza absenta coloana (guard la ~219: if "rar_creds_enc" not in acc_cols: return).
|
||||
# Medii RAR per cont (PRD 5.20 US-001): activare + slot creds + default, per mediu.
|
||||
_migrate_accounts_medii(conn, acc_cols)
|
||||
# US-013: DROP coloana legacy accounts.rar_creds_enc dupa backfill complet.
|
||||
_drop_legacy_accounts_rar_creds(conn, acc_cols)
|
||||
if "active" not in acc_cols:
|
||||
# Conturi existente raman active (default 1).
|
||||
conn.execute("ALTER TABLE accounts ADD COLUMN active INTEGER NOT NULL DEFAULT 1")
|
||||
@@ -229,6 +230,98 @@ def _migrate_accounts_medii(conn: sqlite3.Connection, acc_cols: set[str]) -> Non
|
||||
)
|
||||
|
||||
|
||||
def _drop_legacy_accounts_rar_creds(conn: sqlite3.Connection, acc_cols: set[str]) -> None:
|
||||
"""PRD 5.20 US-013: DROP coloana legacy `accounts.rar_creds_enc` dupa backfill complet.
|
||||
|
||||
Idempotent si sigur la fiecare boot (garda one-way: coloana absenta = nimic de facut).
|
||||
La eroare LOGHEAZA si lasa coloana pe loc (fail-safe — nu crapa boot-ul ambelor procese).
|
||||
Structura separata pentru testabilitate: `_garda_si_drop(conn)` expune pasul de
|
||||
assert+backup+DROP izolat (fara re-backfill 6c), apelabil direct din teste.
|
||||
"""
|
||||
if "rar_creds_enc" not in acc_cols:
|
||||
return # garda one-way: coloana deja dropata sau DB fresh
|
||||
try:
|
||||
_drop_legacy_rar_creds_impl(conn)
|
||||
except Exception as exc:
|
||||
print(
|
||||
f"[db] AVERTISMENT: DROP coloana legacy accounts.rar_creds_enc esuat: {exc}. "
|
||||
"Coloana ramane pe loc (fail-safe).",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
def _drop_legacy_rar_creds_impl(conn: sqlite3.Connection) -> None:
|
||||
"""Re-backfill (AUTO-FIX 6c) + delegate la _garda_si_drop.
|
||||
|
||||
Re-backfill-ul 6c acopera creds setate via POST /v1/conturi/rar-creds intre US-001
|
||||
si US-013 (pot fi DOAR in coloana veche). Ancora globala: AUTOPASS_RAR_ENV.
|
||||
"""
|
||||
if sqlite3.sqlite_version_info < (3, 35, 0):
|
||||
print(
|
||||
f"[db] SQLite {sqlite3.sqlite_version} < 3.35.0 — DROP COLUMN nesuportat; "
|
||||
"coloana legacy accounts.rar_creds_enc ramane.",
|
||||
flush=True,
|
||||
)
|
||||
return
|
||||
|
||||
# AUTO-FIX 6c: re-backfill creds din coloana veche in slotul per-env (ancora globala).
|
||||
# Independent de _migrate_accounts_medii (care sare pe DB deja migrat cu guard newly_added).
|
||||
env = get_settings().rar_env if get_settings().rar_env in ("test", "prod") else "test"
|
||||
slot = f"rar_creds_{env}_enc"
|
||||
conn.execute(
|
||||
f"UPDATE accounts SET {slot}=rar_creds_enc, rar_{env}_enabled=1 "
|
||||
f"WHERE rar_creds_enc IS NOT NULL AND TRIM(rar_creds_enc)<>'' "
|
||||
f"AND ({slot} IS NULL OR TRIM({slot})='')"
|
||||
)
|
||||
|
||||
_garda_si_drop(conn)
|
||||
|
||||
|
||||
def _garda_si_drop(conn: sqlite3.Connection) -> None:
|
||||
"""Garda de siguranta + backup + DROP accounts.rar_creds_enc. Testabila izolat.
|
||||
|
||||
Verifica ca niciun cont nu are creds DOAR in coloana veche (ambele sloturi per-env goale).
|
||||
Daca exista orfane -> NU dropa (fail-safe: fara pierdere de date).
|
||||
Altfel: backup criptat, DROP, verificare PRAGMA (AUTO-FIX 6d).
|
||||
"""
|
||||
# Garda: orfane = cont cu creds in coloana veche DAR ambele sloturi per-env goale.
|
||||
orphan_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM accounts "
|
||||
"WHERE rar_creds_enc IS NOT NULL AND TRIM(rar_creds_enc)<>'' "
|
||||
"AND (rar_creds_test_enc IS NULL OR TRIM(rar_creds_test_enc)='') "
|
||||
"AND (rar_creds_prod_enc IS NULL OR TRIM(rar_creds_prod_enc)='')"
|
||||
).fetchone()[0]
|
||||
|
||||
if orphan_count > 0:
|
||||
print(
|
||||
f"[db] AVERTISMENT: {orphan_count} cont(uri) cu rar_creds_enc ne-copiat in niciun slot "
|
||||
"per-env. DROP anulat (fail-safe: fara pierdere de date).",
|
||||
flush=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Backup criptat inainte de DROP (blob-urile sunt deja criptate Fernet).
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS accounts_rar_creds_enc_backup "
|
||||
"(account_id INTEGER, rar_creds_enc TEXT, backed_up_at TEXT)"
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO accounts_rar_creds_enc_backup "
|
||||
"SELECT id, rar_creds_enc, datetime('now') FROM accounts "
|
||||
"WHERE rar_creds_enc IS NOT NULL"
|
||||
)
|
||||
|
||||
# DROP coloana legacy.
|
||||
conn.execute("ALTER TABLE accounts DROP COLUMN rar_creds_enc")
|
||||
|
||||
# AUTO-FIX 6d: verifica prin PRAGMA (pe tabela accounts, NU grep — submissions are aceeasi coloana).
|
||||
cols_after = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()}
|
||||
if "rar_creds_enc" in cols_after:
|
||||
raise RuntimeError("DROP COLUMN rar_creds_enc esuat: coloana inca prezenta dupa ALTER TABLE")
|
||||
|
||||
print("[db] DROP coloana legacy accounts.rar_creds_enc: OK", flush=True)
|
||||
|
||||
|
||||
def _backfill_submissions_rar_env(conn: sqlite3.Connection) -> None:
|
||||
"""PRD 5.20 US-001 (AUTO-FIX G + E4/3): backfill rar_env + recompute idempotency_key.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user