feat(5.16+5.17): tipografie/antet branded + tipuri cont, planuri si enforcement

PRD 5.16 — propagare design finalizata (system font stack, fara IBM Plex self-hostat):
- US-001/002/008: tokeni --font-ui/--font-mono (system stack) + scala --fs-*; zero
  @font-face si zero /static/fonts/; landing aliniat la acelasi stack
- US-003: RAR online = dot compact in antet + meniu burger; banda rosie DOAR pe blocat
  (invariant zero-silent-failures pastrat)
- US-010: antet "ROMFAST AUTOPASS" + nume service + /login brandeit 2 coloane + badge plan;
  meniu burger cu separatoare; gate strict pe is_authenticated
- US-011: selector tema pill icon+eticheta (reuse THEMES)
- US-004/005/006/007: bug-fix editor prestatii (picker cod+denumire, add_extra in mod
  operatii, cod ales se salveaza fara "+", Renunta inchide via closest)
- US-012/013: landing Autentificare->/login; wizard import colapsat + 4 pasi pe tokeni
- fix VERIFY E2E: contoare duplicate pe 390px (inline display:flex batea @media) -> CSS + test-lock

PRD 5.17 — tipuri de cont + trial Pro 30z + enforcement DUR:
- US-001/002/008: accounts.tier + trial_until (migrare aditiva defensiva); app/plans.py
  sursa unica (PLANS, FREE_MONTHLY_LIMIT=60, effective_tier(now injectabil), monthly_usage,
  CONSUMED_STATUSES); create_account trial Pro 30z; CLI set-tier (protejat id=1, audit)
- US-003/004/005: enforce volum 60/luna INAINTE de build_key pe ambele canale
  (PLAN_LIMITA_LUNARA, 3 niveluri + log_event); gate API Pro+ (PLAN_FARA_API 403 actionabil);
  valideaza/nomenclator raman permise; downgrade lazy; flag AUTOPASS_ENFORCE_PLANS (kill-switch)
- US-006: badge plan antet + linie burger + consum N/60 + warn>=80% + 6 stari + copy RO
  pluralizat + banner one-time trial->Gratuit + pagina Cont

Regresie: 1380 passed, 0 failed, 1 deselected (live). E2E browser pe 390/1280 confirmat.
Backend trimitere (worker/masina stari/idempotenta/contract RAR) NEATINS. Lucrul 5.18
(corpus kNN) ramane separat, necomis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-29 06:01:05 +00:00
parent 9eccb9f6fa
commit c9f9a1ca0e
37 changed files with 3433 additions and 449 deletions

View File

@@ -18,8 +18,9 @@ from __future__ import annotations
import hashlib
import secrets
import sqlite3
from datetime import datetime, timezone
from fastapi import Header, HTTPException, Request
from fastapi import Depends, Header, HTTPException, Request
from .config import get_settings
from .db import get_connection
@@ -162,3 +163,59 @@ def resolve_account_id(
_log_auth_esuat(request, plaintext, "cheie API invalida sau revocata")
raise HTTPException(status_code=401, detail="cheie API invalida sau revocata")
return account_id
def require_api_access(
account_id: int = Depends(resolve_account_id),
) -> int:
"""Dependency FastAPI (T4, PRD 5.17): verifica ca tier-ul efectiv permite accesul la API.
Reguli:
- enforce_plans=False (kill-switch): sare verificarea.
- dev id=1 cu require_api_key=False: bypass (dogfooding, testele existente nu pica).
- Pro/Premium sau trial Pro activ: permit.
- Free/Standard fara trial: 403 PLAN_FARA_API cu eroare 3 niveluri.
Refoloseste resolve_account_id (account_id deja rezolvat din cheie API).
Se ataseaza ca Depends() pe rutele de ingestie API (POST /v1/prezentari,
POST /v1/import, POST /v1/import/{id}/commit). valideaza + nomenclator raman libere.
"""
from .plans import PLANS, effective_tier
from .errors import eroare as _eroare
settings = get_settings()
# Kill-switch operare: sare toate gate-urile de plan.
if not settings.enforce_plans:
return account_id
# Bypass pentru contul implicit dev (id=1) in modul fara cheie API obligatorie.
# In prod (require_api_key=True), id=1 nu are bypass implicit (cheie = obligatorie).
if not settings.require_api_key and account_id == DEFAULT_ACCOUNT_ID:
return account_id
conn = get_connection()
try:
row = conn.execute(
"SELECT tier, trial_until FROM accounts WHERE id=?", (account_id,)
).fetchone()
finally:
conn.close()
now = datetime.now(timezone.utc)
et = effective_tier(row, now)
if not PLANS[et].get("api_access"):
from .observ import log_event
log_event(
"plan_api_refuzat",
account_id=account_id,
nivel="WARNING",
mesaj=f"Acces API refuzat: tier efectiv={et}",
context={"tier_efectiv": et},
)
raise HTTPException(
status_code=403,
detail=_eroare(
"PLAN_FARA_API",
cauza=f"Tier efectiv: {et}. API disponibil pe Pro/Premium.",
),
)
return account_id