# Gateway RAR AUTOPASS Gateway web (Python / FastAPI) care preia prezentarile de service-auto si le declara la **RAR AUTOPASS** (Legea 142/2023, OM 210/2024). Inlocuieste integrarea Visual FoxPro (ROAAUTO). Doua procese peste acelasi SQLite, care comunica prin tabela `submissions`: - **API** (`app.main:app`) — dashboard web, API v1, signup/login, panou admin, `/healthz`, `/metrics`. - **Worker** (`app.worker`) — login RAR, trimite prezentarile din coada, retry/backoff, heartbeat. Trimiterea catre RAR e **dezactivata implicit** (`AUTOPASS_WORKER_SEND_ENABLED=false`) — sigur pentru probe. Sursa de adevar pentru contractul RAR: [`docs/api-rar-contract.md`](docs/api-rar-contract.md). Progres + proces: [`docs/ROADMAP.md`](docs/ROADMAP.md). ## Pornire rapida ```bash pip3 install -r requirements.txt # Python 3.12+ uvicorn app.main:app --reload --port 8010 # API (dashboard /, Swagger /docs) python3 -m app.worker # worker (doar daca vrei sa procesezi coada) ``` La prima pornire se creeaza schema SQLite si se face seed la nomenclatorul RAR — dashboard-ul si maparile merg imediat, offline. Pentru testarea UI-ului si a importului **nu** ai nevoie de worker. Dev rapid fara login: porneste cu `AUTOPASS_WEB_AUTH_REQUIRED=false` (dashboard pe contul implicit id=1). ### Cu `start.sh` (ambaleaza mediu + rol) ```bash ./start.sh test both --send # API + worker, trimite la RAR test (loguri in .run/) ./start.sh test finalizate # listeaza prezentarile inregistrate la RAR (verificare independenta) ./start.sh status # stare procese + /healthz ./start.sh stop # opreste procesele pornite cu "both" ./start-test.sh / ./start-prod.sh # fixeaza mediul (test/prod), forwardeaza rolul ``` ## Pagini web | URL | Ce vezi | |-----|---------| | `/` | Dashboard: coada, prezentari blocate, stare worker, import fisier, mapari, nomenclator | | `/signup` · `/login` | Inregistrare cont (emite cheia API o data) · autentificare | | `/admin` | Panou admin: conturi pe stari, activare/blocare/arhivare (doar admini) | | `/integrare` | Exemple cod (Python/C#/Node/VFP), export Postman/OpenAPI, testeaza conexiunea | | `/docs` | Swagger UI — API v1 interactiv | | `/healthz` · `/metrics` | sanatate JSON · metrici text | ## Import fisier (xlsx / csv) Pe dashboard: **incarca** fisierul → **mapeaza coloanele** (sugerate automat fuzzy; maparea se retine pe semnatura coloanelor, per cont) → **preview** (fiecare rand: `ok` / `needs_mapping` / `needs_data` / `already_sent` / ...) → **confirma** (retastezi numarul de randuri `ok`). Randurile intra in coada. Coloane recunoscute (cu sinonime): `VIN`, `Nr inmatriculare`, `Data prestatie`, `Odometru final`, `Odometru initial`, `Operatie`, `Observatii`. Fiecare cont poate avea mai multe formate memorate. ## API v1 (curl) Dev: fara cheie → cont id=1. Productie (`AUTOPASS_REQUIRE_API_KEY=true`): header `X-API-Key: rfak_...`. ```bash curl -s http://localhost:8010/healthz | python3 -m json.tool # sanatate curl -s http://localhost:8010/v1/nomenclator # coduri RAR (cache local) curl -s http://localhost:8010/v1/prezentari # coada # Trimite o prezentare. `rar_credentials` e OPTIONAL: daca lipseste, worker-ul # foloseste creds-urile RAR salvate pe cont (POST /v1/conturi/rar-creds). Trimite-le # explicit doar cand vrei sa le suprascrii pe acea cerere. curl -s -X POST http://localhost:8010/v1/prezentari \ -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \ -d '{ "prezentari": [{ "vin": "WAUZZZ8K0AA000001", "nr_inmatriculare": "B123ABC", "data_prestatie": "2026-06-15", "odometru_final": "120000", "prestatii": [{"cod_op_service": "REVIZIE PERIODICA", "denumire": "REVIZIE PERIODICA"}] }] }' # Dry-run: valideaza payload + mapare, FARA enqueue, FARA creds curl -s -X POST http://localhost:8010/v1/prezentari/valideaza \ -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' -d '{ "prezentari": [ ... ] }' # Import fisier curl -s -X POST http://localhost:8010/v1/import -H 'X-API-Key: rfak_...' -F 'file=@import.xlsx' ``` Toate endpointurile sunt in `/docs`. Exemple gata facute + Postman/OpenAPI: hub-ul `/integrare`. ## Conturi si chei API Fiecare service = un **cont** (`accounts`) cu lifecycle (`pending → active → blocked / archived / deleted`). Worker-ul trimite doar pentru conturi `active`. Web-ul se autentifica prin **sesiune** (login email+parola), API-ul prin **cheie API** (`X-API-Key`). Cheia identifica contul, e separata de credentialele RAR. ```bash # Self-onboarding: service-ul deschide /signup → primeste cheia o data. Primul cont = admin. # Sau din CLI (admin, pe masina gateway-ului): python3 -m tools.account create --name "Service Auto SRL" --cui RO12345678 --with-key python3 -m tools.account list [--pending] | activate --account N | set-admin --account N python3 -m tools.apikey create|list|rotate|revoke --account N # cheie afisata O SINGURA DATA ``` **Creds RAR per cont** (ca worker-ul sa trimita fara parola in fiecare cerere) — criptate Fernet at-rest: ```bash curl -s -X POST http://localhost:8010/v1/conturi/rar-creds \ -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \ -d '{"email": "service@exemplu.ro", "password": "parola-rar"}' ``` > GET-urile de listare (`/v1/prezentari`, `/v1/nomenclator`, `/v1/audit/export`) sunt momentan > **globale si neprotejate** — filtrarea pe cont ramane de adaugat. ## Proba reala la RAR (mediu test) 1. Pune creds de test in `settings.xml` (copiaza din `settings.xml.example`, bloc ``; **nu** se comite). `settings.xml` tine un singur cont RAR doar pentru dev/test — creds-urile conturilor reale stau criptate in DB. 2. Baga prezentari in coada (import sau API). 3. `./start.sh test worker --send` — worker-ul trimite si trece fiecare rand in `sent` (cu `id_prezentare`), `needs_data` sau `error`. 4. Verifica: dashboard, `curl /v1/prezentari`, sau `./start.sh test finalizate` (listeaza direct de la RAR). > `sent` + `id_prezentare` = RAR a acceptat. La raspuns pierdut, worker-ul reconciliaza anti-duplicat > (cauta in finalizate, marcheaza `sent` fara re-trimitere). `FINALIZATA` e terminal la RAR. ## Configurare (`AUTOPASS_*`) | Variabila | Implicit | Rol | |-----------|----------|-----| | `DB_PATH` | `./data/autopass.db` | calea SQLite | | `RAR_ENV` | `test` | `test` / `prod` | | `REQUIRE_API_KEY` | `false` | `true` = cere cheie pe `/v1/*` (prod) | | `WEB_AUTH_REQUIRED` | `true` | `false` = dashboard fara login, cont id=1 (dev) | | `CREDS_KEY` | (efemera) | **cheie Fernet creds RAR — trebuie PARTAJATA intre API si worker** | | `SESSION_SECRET` | (efemer) | secret cookie sesiune; persistent in prod | | `WORKER_SEND_ENABLED` | `false` | `true` = trimite efectiv la RAR | | `SMTP_HOST` (+ `_PORT`/`_USER`/`_PASSWORD`/`_FROM`) | (none) | notificare admin la signup (best-effort) | Genereaza chei: `python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` (CREDS_KEY) si `python3 -c "import secrets; print(secrets.token_hex(32))"` (SESSION_SECRET). ## Teste ```bash python3 -m pytest -q # toata suita python3 -m pytest tests/test_x.py -q # un fisier ``` ## Docker / deploy ```bash cp .env.example .env # CRITIC: completeaza AUTOPASS_CREDS_KEY (partajata api+worker) docker compose up --build # api (:8010) + worker + autoheal, acelasi image + volum SQLite ``` ## Structura ``` app/ main.py # FastAPI: API v1 + dashboard + auth + admin api/v1/ # router.py (prezentari, valideaza, nomenclator, mapari, conturi), # import_router.py, integrare_router.py (ping, postman/openapi) web/ # routes.py (dashboard + import HTMX), auth_routes.py, admin_routes.py, # session.py, csrf.py, labels.py, templates/, static/ worker/ # proces separat: login RAR, send, retry, heartbeat rar_client.py # client HTTP RAR (login/JWT, postPrezentare, nomenclator) auth.py users.py accounts.py # chei API, parole scrypt + admin, lifecycle conturi validation.py mapping.py errors.py crypto.py # validare, mapare cod, erori 3-niveluri, Fernet schema.sql # schema SQLite tools/ # CLI admin: account, apikey, backup, rar_finalizate, import_dbf docs/ # contract RAR + ROADMAP + prd/ tests/ legacy-vfp/ # suita pytest · arhiva ROAAUTO (referinta) ```