feat(api): rar_credentials optional pe POST /v1/prezentari

Cand `rar_credentials` lipseste din cerere, submission-ul intra fara creds
efemere, iar worker-ul cade pe creds-urile RAR durabile ale contului
(accounts.rar_creds_enc). Identificarea contului ramane pe cheia API.
Trimiterea explicita a creds-urilor suprascrie creds-urile contului pe acea
cerere (back-compat: fluxul vechi ROAAUTO merge identic).

- models.py: rar_credentials: RarCredentials | None = None
- router.py: cripteaza creds doar daca exista (altfel creds_enc=NULL)
- worker NEATINS: avea deja fallback _creds_for(...) or _creds_from_account(...)

Pagina /integrare aliniata: exemplele cod (7 limbaje) + export Postman nu mai
includ rar_credentials in payload; nota noua explica modelul (creds pe cont,
optional in payload). README rescris compact + reflecta optionalitatea.

Test nou: enqueue fara creds -> submission fara creds efemere -> fallback pe
contul cu creds salvate. Suita: 673 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-23 13:39:53 +00:00
parent 0517ae59fb
commit 5dc963a02c
8 changed files with 188 additions and 375 deletions

View File

@@ -94,12 +94,10 @@ _POSTMAN_ITEMS = [
"body": {
"mode": "raw",
"options": {"raw": {"language": "json"}},
# rar_credentials e optional: cererea trimite doar cheia API + datele
# prezentarii; worker-ul foloseste creds-urile RAR salvate pe cont.
"raw": (
'{\n'
' "rar_credentials": {\n'
' "email": "user@exemplu.ro",\n'
' "password": "parola_rar"\n'
' },\n'
' "prezentari": [\n'
' {\n'
' "vin": "WVWZZZ1KZAW000123",\n'

View File

@@ -60,12 +60,15 @@ def create_prezentari(
implicit id=1 in dev fara cheie, 401 fara cheie valida in prod.
Nota: rar_credentials NU se persista (zero-storage) — worker-ul le va primi
pe alt canal (T2); in schelet enqueue-ul doar stocheaza prezentarea.
Cand rar_credentials lipseste, submission-ul intra fara creds efemere: worker-ul
cade pe creds-urile durabile ale contului (`accounts.rar_creds_enc`).
"""
acct = account_or_default(account_id)
# Creds RAR efemere: criptate si lipite de fiecare submission nou pana la
# primul login reusit pentru cont (worker le sterge atunci). Zero-storage at
# rest — niciodata in clar in DB/loguri (plan sect. 5).
creds_enc = encrypt_creds(req.rar_credentials.model_dump())
# rest — niciodata in clar in DB/loguri (plan sect. 5). Optional: cand lipsesc,
# creds_enc=NULL si worker-ul foloseste creds-urile durabile ale contului.
creds_enc = encrypt_creds(req.rar_credentials.model_dump()) if req.rar_credentials else None
conn = get_connection()
results: list[SubmissionResult] = []
try:

View File

@@ -80,9 +80,14 @@ class PrezentareIn(BaseModel):
class PrezentareRequest(BaseModel):
"""Body pentru POST /v1/prezentari — una sau mai multe prezentari + creds RAR."""
"""Body pentru POST /v1/prezentari — una sau mai multe prezentari + creds RAR.
rar_credentials: RarCredentials
`rar_credentials` e OPTIONAL: daca lipseste, worker-ul foloseste creds-urile RAR
durabile salvate pe cont (`accounts.rar_creds_enc`, via POST /v1/conturi/rar-creds).
Trimite-le explicit doar cand vrei sa suprascrii creds-urile contului pe acea cerere.
"""
rar_credentials: RarCredentials | None = None
prezentari: list[PrezentareIn] = Field(..., min_length=1)

View File

@@ -27,6 +27,10 @@ def _payload_prezentari_dict(account_id: int) -> dict:
Campurile cu default (odometru_initial, obs, b64_image, sistem_reparat) sunt
omise pentru concizie — nu sunt obligatorii.
`rar_credentials` NU e inclus: cererea trimite doar cheia API + datele prezentarii,
iar worker-ul foloseste credentialele RAR salvate pe cont (tab-ul Cont). Trimiterea
lor in payload e optionala (suprascrie creds-urile contului pe acea cerere).
"""
# Construim un dict cu toate campurile obligatorii
campuri = _campuri_obligatorii()
@@ -48,13 +52,7 @@ def _payload_prezentari_dict(account_id: int) -> dict:
# Fallback generic pentru campuri neasteptate adaugate ulterior
prezentare[camp] = f"<{camp}>"
return {
"rar_credentials": {
"email": "utilizator@service.ro",
"password": "parola_rar",
},
"prezentari": [prezentare],
}
return {"prezentari": [prezentare]}
def _payload_json_str(account_id: int, indent: int = 2) -> str:

View File

@@ -26,6 +26,11 @@
<span class="muted" style="font-size:13px; margin-left:16px;">Endpoint:</span>
<code style="font-size:12px; color:var(--accent);">{{ base_url }}</code>
</div>
<p class="muted" style="font-size:12px; margin:10px 0 0;">
Cererile trimit doar cheia API + datele prezentarii. Credentialele RAR se configureaza
o data in <a href="/?tab=cont">Cont</a> si sunt folosite automat la trimitere. Optional,
poti include <code>rar_credentials</code> in payload ca sa le suprascrii pe acea cerere.
</p>
</div>
{# Tab-list PRIMAR: limbaje #}