feat(web): hub integrare /integrare — exemple cod + retetar VFP + ping + export (PRD 5.1)
Pagina /integrare (tab autentificat, scoped pe cont): exemple cod multi-limbaj (curl/Python/PHP/C#/Node) + retetar Visual FoxPro (MSXML2 + WinHttp) pe ambele canale (prezentari JSON + import fisier), export Postman/OpenAPI/Swagger si buton "Testeaza conexiunea". - US-001: GET /v1/ping (readiness: account_id/mediu/autentificat_cu_cheie/ are_creds_rar/ts) + GET /v1/integrare/postman.json (v2.1.0, allowlist 3 rute) - US-002: app/web/integrare_examples.py pur (7 limbaje x 2 canale, drift-test is_required(), JSON compact pentru C#/VFP) - US-003: tab "Integrare" IA pe 2 niveluri (limbaj->canal, VFP cu dialecte), copy din <pre><code>, empty-state CTA, export .cardlink, script scoped - US-004: POST /integrare/test-cheie (account_for_key direct, scoped sesiune, no-echo cheie) Backend trimitere (worker/masina stari/idempotenta/mapping) si schema neatinse. 568 teste pass. VERIFY context curat + E2E browser (Playwright) + code-review high. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,7 +134,7 @@ def _rar_state(hb, worker_alive: bool) -> str:
|
||||
# cade pe Acasa (tab invalid -> fallback "acasa" in dashboard()), fara 404.
|
||||
# US-003 (3.6): "coada" (Trimiteri) nu mai e tab — Trimiterile sunt sectiune pe Acasa.
|
||||
# ?tab=coada cade tot pe Acasa (fallback), fara 404, fara fragment orfan.
|
||||
_TABS_VALIDE = {"acasa", "mapari", "cont", "nomenclator"}
|
||||
_TABS_VALIDE = {"acasa", "mapari", "cont", "nomenclator", "integrare"}
|
||||
|
||||
|
||||
def _get_acasa_context(request: Request, conn, account_id: int) -> dict:
|
||||
@@ -247,6 +247,41 @@ def _render_panel_nomenclator(request: Request, conn) -> str:
|
||||
})
|
||||
|
||||
|
||||
def _render_integrare(request: Request, conn, account_id: int) -> str:
|
||||
"""Randeaza panoul Integrare ca string HTML (hub documentatie + exemple cod).
|
||||
|
||||
Calculeaza are_cheie (chei API active pe cont) si are_creds (credentiale RAR
|
||||
configurate pe cont), preia base_url real si genereaza snippet-uri multi-limbaj.
|
||||
"""
|
||||
from ..mapping import account_or_default
|
||||
from .integrare_examples import exemple as _exemple
|
||||
|
||||
acct = account_or_default(account_id)
|
||||
row_creds = conn.execute(
|
||||
"SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)
|
||||
).fetchone()
|
||||
are_creds = bool(row_creds and row_creds["rar_creds_enc"])
|
||||
|
||||
row_key = conn.execute(
|
||||
"SELECT 1 FROM api_keys WHERE account_id=? AND active=1 LIMIT 1", (acct,)
|
||||
).fetchone()
|
||||
are_cheie = row_key is not None
|
||||
|
||||
base_url = str(request.base_url).rstrip("/")
|
||||
ex = _exemple(base_url, acct)
|
||||
csrf_token = get_csrf_token(request)
|
||||
|
||||
return templates.get_template("_integrare.html").render({
|
||||
"request": request,
|
||||
"account_id": acct,
|
||||
"base_url": base_url,
|
||||
"exemple": ex,
|
||||
"are_cheie": are_cheie,
|
||||
"are_creds": are_creds,
|
||||
"csrf_token": csrf_token,
|
||||
})
|
||||
|
||||
|
||||
def _render_panel_for_tab(request: Request, conn, account_id: int, tab: str) -> str:
|
||||
"""Randeaza panoul corespunzator unui tab ca string HTML."""
|
||||
if tab == "acasa":
|
||||
@@ -261,6 +296,8 @@ def _render_panel_for_tab(request: Request, conn, account_id: int, tab: str) ->
|
||||
return _render_panel_cont(request, conn, account_id)
|
||||
if tab == "nomenclator":
|
||||
return _render_panel_nomenclator(request, conn)
|
||||
if tab == "integrare":
|
||||
return _render_integrare(request, conn, account_id)
|
||||
return _render_panel_acasa(request)
|
||||
|
||||
|
||||
@@ -346,6 +383,18 @@ def fragment_nomenclator(request: Request) -> HTMLResponse:
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.get("/_fragments/integrare", response_class=HTMLResponse)
|
||||
def fragment_integrare(request: Request) -> HTMLResponse:
|
||||
"""Fragment HTMX pentru tab-ul Integrare (hub documentatie + exemple cod)."""
|
||||
account_id = require_login(request)
|
||||
conn = get_connection()
|
||||
try:
|
||||
html = _render_integrare(request, conn, account_id)
|
||||
return HTMLResponse(content=html)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.get("/_fragments/banner", response_class=HTMLResponse)
|
||||
def fragment_banner(request: Request) -> HTMLResponse:
|
||||
account_id = require_login(request)
|
||||
@@ -1938,6 +1987,62 @@ def cont_roteste_cheie(
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.post("/integrare/test-cheie", response_class=HTMLResponse)
|
||||
def integrare_test_cheie(
|
||||
request: Request,
|
||||
api_key: str = Form(""),
|
||||
csrf_token: str | None = Form(None),
|
||||
) -> HTMLResponse:
|
||||
"""Verifica cheia API lipita de utilizator — scoped pe contul sesiunii.
|
||||
|
||||
US-004 (PRD Etapa 5): permite utilizatorului sa confirme ca o cheie copiata
|
||||
din generatorul de exemple corespunde contului sau, fara efecte secundare
|
||||
(fara creare/rotire). Cheie goala, invalida sau a altui cont -> mesaj de
|
||||
eroare neutru (fara eco al cheii in raspuns).
|
||||
"""
|
||||
account_id = require_login(request)
|
||||
verify_csrf(request, csrf_token)
|
||||
acct = account_or_default(account_id)
|
||||
|
||||
# Validare cheie goala / doar spatii -> eroare inainte de DB
|
||||
cheie = (api_key or "").strip()
|
||||
if not cheie:
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": False, "mesaj": "Cheia este goala sau lipseste."},
|
||||
)
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
from ..auth import account_for_key
|
||||
cont_cheie = account_for_key(conn, cheie)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if cont_cheie is None:
|
||||
# Cheie invalida, inexistenta sau revocata
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": False,
|
||||
"mesaj": "Cheie invalida sau revocata — nu a fost gasita in sistem."},
|
||||
)
|
||||
|
||||
if cont_cheie != acct:
|
||||
# Cheie valida dar apartine altui cont — mesaj neutru, fara dezvaluire cont terta
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": False,
|
||||
"mesaj": "Cheia nu apartine contului tau."},
|
||||
)
|
||||
|
||||
# Succes — cheia e activa si corespunde contului sesiunii
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": True,
|
||||
"mesaj": f"Cheie valida — cont {acct}."},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/cont/rar-creds", response_class=HTMLResponse)
|
||||
def cont_rar_creds(
|
||||
request: Request,
|
||||
|
||||
Reference in New Issue
Block a user