|
|
|
|
@@ -1,288 +1,111 @@
|
|
|
|
|
# Gateway RAR AUTOPASS
|
|
|
|
|
|
|
|
|
|
Gateway web (Python / FastAPI) care preia prezentarile de service si le declara la
|
|
|
|
|
**RAR AUTOPASS** (Legea 142/2023, OM 210/2024). Inlocuieste integrarea Visual FoxPro
|
|
|
|
|
existenta (ROAAUTO). Sursa de adevar pentru contractul RAR este
|
|
|
|
|
[`docs/api-rar-contract.md`](docs/api-rar-contract.md).
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
## Arhitectura pe scurt
|
|
|
|
|
Doua procese peste acelasi SQLite, care comunica prin tabela `submissions`:
|
|
|
|
|
|
|
|
|
|
Doua procese peste acelasi SQLite persistent:
|
|
|
|
|
- **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.
|
|
|
|
|
|
|
|
|
|
| Proces | Rol | Pornire |
|
|
|
|
|
|--------|-----|---------|
|
|
|
|
|
| **API** (`app.main:app`) | API v1 (`/v1/*`), dashboard web (`/`), `/healthz`, `/metrics`, import fisiere (`/v1/import/*`) | `uvicorn app.main:app` |
|
|
|
|
|
| **Worker** (`app.worker`) | login RAR + JWT, refresh nomenclator, trimite prezentarile din coada, retry/backoff, heartbeat | `python3 -m app.worker` |
|
|
|
|
|
Trimiterea catre RAR e **dezactivata implicit** (`AUTOPASS_WORKER_SEND_ENABLED=false`) — sigur pentru probe.
|
|
|
|
|
|
|
|
|
|
Worker-ul ruleaza ca **proces separat** (nu task in API) — un worker mort nu trebuie sa
|
|
|
|
|
lase containerul "sanatos". Comunicarea API <-> worker se face exclusiv prin tabela
|
|
|
|
|
`submissions` din SQLite. Send-ul catre RAR este **dezactivat 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).
|
|
|
|
|
|
|
|
|
|
## Cerinte
|
|
|
|
|
|
|
|
|
|
- Python 3.12+
|
|
|
|
|
- Dependintele din `requirements.txt`
|
|
|
|
|
## Pornire rapida
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
pip3 install -r requirements.txt
|
|
|
|
|
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)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
(Optional, pentru deploy: Docker + Docker Compose — vezi sectiunea Docker.)
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
## Configurare
|
|
|
|
|
Dev rapid fara login: porneste cu `AUTOPASS_WEB_AUTH_REQUIRED=false` (dashboard pe contul implicit id=1).
|
|
|
|
|
|
|
|
|
|
Variabilele de mediu folosesc prefixul `AUTOPASS_`. Pentru dev local valorile implicite
|
|
|
|
|
sunt suficiente — **nu** ai nevoie de `.env` sau de credentiale RAR ca sa testezi UI-ul si
|
|
|
|
|
API-ul. Copiaza `.env.example` -> `.env` doar cand vrei sa rulezi end-to-end.
|
|
|
|
|
|
|
|
|
|
| Variabila | Implicit | Rol |
|
|
|
|
|
|-----------|----------|-----|
|
|
|
|
|
| `AUTOPASS_DB_PATH` | `./data/autopass.db` | calea fisierului SQLite |
|
|
|
|
|
| `AUTOPASS_RAR_ENV` | `test` | `test` sau `prod` |
|
|
|
|
|
| `AUTOPASS_REQUIRE_API_KEY` | `false` | `false` = dev (fara cheie -> cont id=1); `true` = prod (cere cheie) |
|
|
|
|
|
| `AUTOPASS_CREDS_KEY` | (efemera) | cheie Fernet pt criptarea creds RAR. **Trebuie partajata intre API si worker.** Genereaza: `python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |
|
|
|
|
|
| `AUTOPASS_WORKER_SEND_ENABLED` | `false` | `true` = trimite efectiv la RAR (proba end-to-end) |
|
|
|
|
|
| `AUTOPASS_WORKER_USE_TEST_CREDS` | `false` | dev: foloseste blocul `<test>` din `settings.xml` pt login worker |
|
|
|
|
|
|
|
|
|
|
Pentru proba reala cu RAR: copiaza `settings.xml.example` -> `settings.xml` si completeaza
|
|
|
|
|
credentialele de test (fisierul **nu** se comite).
|
|
|
|
|
|
|
|
|
|
## Rulare locala (dezvoltare)
|
|
|
|
|
|
|
|
|
|
### 1. Porneste API-ul
|
|
|
|
|
### Cu `start.sh` (ambaleaza mediu + rol)
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
uvicorn app.main:app --reload --port 8010
|
|
|
|
|
# sau, daca uvicorn nu e pe PATH:
|
|
|
|
|
python3 -m uvicorn app.main:app --reload --port 8010
|
|
|
|
|
./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
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
La prima pornire se creeaza schema SQLite si se face seed la nomenclatorul RAR (18 coduri
|
|
|
|
|
din contract), astfel incat dashboard-ul si maparile functioneaza imediat, offline.
|
|
|
|
|
|
|
|
|
|
### 2. (Optional) Porneste worker-ul
|
|
|
|
|
|
|
|
|
|
Necesar doar pentru a procesa coada / a trimite la RAR. Pentru testarea UI-ului si a
|
|
|
|
|
import-ului **nu** e necesar.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
python3 -m app.worker
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Pornire rapida cu `start.sh`
|
|
|
|
|
|
|
|
|
|
`start.sh` ambaleaza pornirea pe mediu (`test` / `prod`) si rol (`api` / `worker` / `both`):
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./start.sh test api # API pe :8010, mediu test
|
|
|
|
|
./start.sh test worker --send # worker care TRIMITE la RAR test
|
|
|
|
|
./start.sh test both --send # API + worker impreuna (dev end-to-end, loguri in .run/)
|
|
|
|
|
./start.sh prod api --port 8010 # API mediu prod
|
|
|
|
|
./start.sh prod worker --send # worker prod (NU foloseste creds de test)
|
|
|
|
|
./start.sh status # stare procese + /healthz
|
|
|
|
|
./start.sh stop # opreste procesele pornite cu "both"
|
|
|
|
|
./start.sh test finalizate # ce prezentari sunt inregistrate la RAR (vezi mai jos)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Optiuni: `--port N`, `--host H`, `--reload` (dev), `--send` (activeaza trimiterea la RAR),
|
|
|
|
|
`--test-creds` / `--no-test-creds` (forteaza folosirea creds `<test>` din `settings.xml`).
|
|
|
|
|
Pe `test` cu `--send`, creds `<test>` se folosesc automat. Pentru productie reala foloseste
|
|
|
|
|
`docker compose` (vezi sectiunea Docker).
|
|
|
|
|
|
|
|
|
|
Doua wrappere fixeaza mediul si forwardeaza rolul + optiunile:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./start-test.sh # = start.sh test both --send (API + worker, trimite la RAR test)
|
|
|
|
|
./start-test.sh worker --send # = start.sh test worker --send
|
|
|
|
|
./start-test.sh finalizate # = start.sh test finalizate
|
|
|
|
|
./start-prod.sh both --send # = start.sh prod both --send
|
|
|
|
|
./start-prod.sh api # = start.sh prod api
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Pe test, `./start-test.sh` fara argumente porneste end-to-end (sandbox RAR e sigur). Pe prod,
|
|
|
|
|
`./start-prod.sh` cere rolul explicit si trimiterea trebuie ceruta cu `--send` (evita trimiteri
|
|
|
|
|
accidentale in productie).
|
|
|
|
|
|
|
|
|
|
## Testare in browser
|
|
|
|
|
|
|
|
|
|
Cu API-ul pornit, deschide in browser:
|
|
|
|
|
## Pagini web
|
|
|
|
|
|
|
|
|
|
| URL | Ce vezi |
|
|
|
|
|
|-----|---------|
|
|
|
|
|
| `http://localhost:8010/` | **Dashboard** — stare coada, banner prezentari blocate, stare worker / ultim login RAR, editor mapari operatii, browser nomenclator, sectiune **import fisier** |
|
|
|
|
|
| `http://localhost:8010/docs` | **Swagger UI** — API v1 interactiv (incearca endpointurile direct din browser) |
|
|
|
|
|
| `http://localhost:8010/healthz` | JSON sanatate: worker viu, ultim login RAR, adancime coada |
|
|
|
|
|
| `http://localhost:8010/metrics` | metrici text (submissions pe status) |
|
|
|
|
|
| `/` | 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 |
|
|
|
|
|
|
|
|
|
|
### Fluxul de import fisier (xlsx / csv) din browser
|
|
|
|
|
## Import fisier (xlsx / csv)
|
|
|
|
|
|
|
|
|
|
Pe dashboard, in sectiunea de import:
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
1. **Incarca** un fisier `.xlsx` sau `.csv` (drag & drop sau selectare).
|
|
|
|
|
2. **Mapeaza coloanele** — gateway-ul sugereaza automat (fuzzy) maparea coloana fisier ->
|
|
|
|
|
camp canonic (VIN, data prestatie, odometru, operatie etc.). Maparea se retine pe
|
|
|
|
|
semnatura coloanelor: la urmatorul fisier cu aceleasi coloane se aplica automat.
|
|
|
|
|
3. **Preview** — fiecare rand primeste o stare: `ok`, `needs_mapping`, `needs_data`,
|
|
|
|
|
`needs_review`, `already_sent`, `duplicate_in_file`.
|
|
|
|
|
4. **Confirma** — gate dur: retastezi numarul exact de randuri `ok` de trimis. Randurile
|
|
|
|
|
confirmate intra in coada (`submissions`), apoi le urmaresti in tabelul de jos.
|
|
|
|
|
Coloane recunoscute (cu sinonime): `VIN`, `Nr inmatriculare`, `Data prestatie`, `Odometru final`,
|
|
|
|
|
`Odometru initial`, `Operatie`, `Observatii`. Fiecare cont poate avea mai multe formate memorate.
|
|
|
|
|
|
|
|
|
|
Coloane recunoscute (cu sinonime): `VIN`, `Nr inmatriculare`, `Data prestatie`,
|
|
|
|
|
`Odometru final`, `Odometru initial`, `Operatie`, `Observatii`.
|
|
|
|
|
## API v1 (curl)
|
|
|
|
|
|
|
|
|
|
### Genereaza un fisier de test pentru import
|
|
|
|
|
|
|
|
|
|
Repo-ul nu contine fisiere sample. Creeaza unul rapid:
|
|
|
|
|
Dev: fara cheie → cont id=1. Productie (`AUTOPASS_REQUIRE_API_KEY=true`): header `X-API-Key: rfak_...`.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
import openpyxl
|
|
|
|
|
wb = openpyxl.Workbook()
|
|
|
|
|
ws = wb.active
|
|
|
|
|
ws.append(["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"])
|
|
|
|
|
ws.append(["WAUZZZ8K0AA000001", "B123ABC", "2026-06-15", 120000, "REVIZIE PERIODICA"])
|
|
|
|
|
ws.append(["WAUZZZ8K0AA000002", "B456DEF", "2026-06-16", 85000, "REPARATIE"])
|
|
|
|
|
wb.save("sample_import.xlsx")
|
|
|
|
|
print("scris sample_import.xlsx")
|
|
|
|
|
PY
|
|
|
|
|
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'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Sau un CSV echivalent:
|
|
|
|
|
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
|
|
|
|
|
printf 'VIN,Nr inmatriculare,Data prestatie,Odometru final,Operatie\nWAUZZZ8K0AA000001,B123ABC,2026-06-15,120000,REVIZIE PERIODICA\n' > sample_import.csv
|
|
|
|
|
# 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
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Incarca apoi fisierul prin sectiunea de import a dashboard-ului.
|
|
|
|
|
|
|
|
|
|
## Proba trimitere la RAR (mediu test) + verificare ca au ajuns
|
|
|
|
|
|
|
|
|
|
Implicit worker-ul **nu** trimite (`AUTOPASS_WORKER_SEND_ENABLED=false`). Pentru proba
|
|
|
|
|
end-to-end pe contul de test RAR:
|
|
|
|
|
|
|
|
|
|
1. Pune credentialele de test in `settings.xml` (copiaza din `settings.xml.example`,
|
|
|
|
|
completeaza blocul `<test>`). Acestea **nu** se comit.
|
|
|
|
|
|
|
|
|
|
2. Bag-a prezentari in coada — fie prin import fisier din dashboard, fie prin API
|
|
|
|
|
(`POST /v1/prezentari`, vezi mai jos).
|
|
|
|
|
|
|
|
|
|
3. Porneste worker-ul cu trimiterea activa:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./start.sh test worker --send
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Worker-ul face login la RAR test, ia randurile `queued`, trimite si trece fiecare rand
|
|
|
|
|
in `sent` cu `id_prezentare` (id-ul intors de RAR — dovada ca a ajuns) sau in
|
|
|
|
|
`needs_data` / `error` cu motivul.
|
|
|
|
|
|
|
|
|
|
4. **Vizualizeaza prezentarile trimise** — trei feluri:
|
|
|
|
|
|
|
|
|
|
- **Dashboard** (`http://localhost:8010/`) — tabelul de jos arata fiecare submission cu
|
|
|
|
|
status (`sent`/`error`/...), `id_prezentare`, cod RAR si eroare. Se actualizeaza singur.
|
|
|
|
|
- **API**: `curl -s http://localhost:8010/v1/prezentari` — coada locala cu statusuri.
|
|
|
|
|
- **Direct de la RAR** (confirmare independenta ca au ajuns):
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./start.sh test finalizate
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Face login la RAR test si listeaza prezentarile inregistrate acolo (id, VIN, data,
|
|
|
|
|
odometru). Compari `id`-urile cu `id_prezentare` din coada locala: daca se regasesc,
|
|
|
|
|
prezentarea a ajuns la RAR.
|
|
|
|
|
|
|
|
|
|
> Status `sent` + `id_prezentare` completat = RAR a acceptat prezentarea. Worker-ul are si
|
|
|
|
|
> reconciliere anti-duplicat: daca raspunsul RAR se pierde, la urmatorul ciclu cauta
|
|
|
|
|
> prezentarea in finalizate si o marcheaza `sent` fara a o re-trimite.
|
|
|
|
|
|
|
|
|
|
## Import fisier pentru mai multi utilizatori (service-uri) cu formate diferite
|
|
|
|
|
|
|
|
|
|
Da — fiecare service auto poate avea propriul format de fisier (alte denumiri de coloane,
|
|
|
|
|
alta ordine, alt format de data). Sistemul **tine minte maparea per cont**, deci nu o refaci
|
|
|
|
|
la fiecare upload:
|
|
|
|
|
|
|
|
|
|
- **Cont (`account_id`)** — fiecare service e un cont. In productie contul se identifica prin
|
|
|
|
|
**cheia API** (`X-API-Key`) trimisa la upload/cerere (`AUTOPASS_REQUIRE_API_KEY=true`). In
|
|
|
|
|
dev, fara cheie, totul merge pe contul implicit `id=1`.
|
|
|
|
|
|
|
|
|
|
- **Semnatura coloanelor** — la upload, gateway-ul calculeaza o semnatura din lista (sortata)
|
|
|
|
|
a denumirilor de coloane din fisier. Maparea coloana-fisier -> camp-canonic se salveaza in
|
|
|
|
|
tabela `column_mappings`, cheie unica `(account_id, signature_coloane)`, impreuna cu
|
|
|
|
|
formatul de data.
|
|
|
|
|
|
|
|
|
|
- **Re-aplicare automata** — la urmatorul fisier cu **aceleasi coloane** (aceeasi semnatura),
|
|
|
|
|
pentru **acelasi cont**, maparea retinuta se aplica automat si sari direct la preview. Daca
|
|
|
|
|
un service schimba formatul (alte coloane) se creeaza o semnatura noua, deci o mapare noua —
|
|
|
|
|
fara sa o strice pe cea veche. Astfel un cont poate avea mai multe formate memorate simultan.
|
|
|
|
|
|
|
|
|
|
Pe scurt: **cine** = `account_id` (din cheia API), **care format** = `signature_coloane`
|
|
|
|
|
(setul de coloane al fisierului). Combinatia lor selecteaza maparea corecta.
|
|
|
|
|
|
|
|
|
|
## Conturi (service-uri) si chei API
|
|
|
|
|
|
|
|
|
|
Un **cont** (`accounts`) = un service auto care foloseste gateway-ul. Cererile `/v1/*` se
|
|
|
|
|
autentifica printr-o **cheie API** (header `X-API-Key: <cheie>` sau `Authorization: Bearer
|
|
|
|
|
<cheie>`) care identifica contul. Cheia e separata de credentialele RAR ale service-ului.
|
|
|
|
|
|
|
|
|
|
Enforcement-ul e controlat de `AUTOPASS_REQUIRE_API_KEY`:
|
|
|
|
|
- `false` (dev/test, implicit): cerere fara cheie -> contul implicit `id=1`; o cheie prezenta
|
|
|
|
|
dar invalida -> `401`.
|
|
|
|
|
- `true` (productie): orice `/v1/*` **protejat** cere o cheie valida, altfel `401`.
|
|
|
|
|
|
|
|
|
|
Auth-ul se aplica pe endpointurile care scriu/sunt legate de cont (au dependinta de cheie):
|
|
|
|
|
`POST /v1/prezentari`, `POST /v1/mapari`, `POST|DELETE /v1/conturi/rar-creds` si toate rutele
|
|
|
|
|
de import (`POST /v1/import`, `.../column-mapping`, `.../preview`, `.../commit`,
|
|
|
|
|
`.../export-failed`) — acestea ruleaza pe `account_id`-ul cheii. GET-urile de **monitorizare**
|
|
|
|
|
(`/v1/prezentari`, `/v1/prezentari/{id}`, `/v1/nomenclator`, `/v1/mapari`, `/v1/audit/export`)
|
|
|
|
|
sunt momentan **neprotejate si globale** (nu filtreaza pe cont). Filtrarea pe cont a listarilor
|
|
|
|
|
+ protejarea lor raman de adaugat (vezi tabelul de mai jos).
|
|
|
|
|
|
|
|
|
|
### Stare implementare
|
|
|
|
|
|
|
|
|
|
| Capabilitate | Stare | Cum |
|
|
|
|
|
|--------------|-------|-----|
|
|
|
|
|
| Emitere / rotire / revocare / listare chei API | **Implementat** | CLI `python3 -m tools.apikey` |
|
|
|
|
|
| Auth pe cheie (X-API-Key / Bearer) pe POST-uri + import | **Implementat** | `app/auth.py` + flag `AUTOPASS_REQUIRE_API_KEY` |
|
|
|
|
|
| Ingestie + import account-scoped (din cheie) | **Implementat** | `POST /v1/prezentari`, `POST /v1/import` |
|
|
|
|
|
| Creds RAR durabile per cont | **Implementat** | `POST /v1/conturi/rar-creds` |
|
|
|
|
|
| Creare cont nou (service) | **De facut / manual** | momentan prin `INSERT` SQL (vezi mai jos); nu exista tool/endpoint dedicat |
|
|
|
|
|
| Protejare + filtrare pe cont a GET-urilor de listare | **De facut** | `GET /v1/prezentari`, `/v1/nomenclator`, `/v1/audit/export` sunt globale acum |
|
|
|
|
|
| Self-onboarding web (login email+parola -> emite cheie) | **De facut** | `docs/ROADMAP.md` (Etapa 3.3) — neimplementat |
|
|
|
|
|
|
|
|
|
|
> Lifecycle-ul cheilor se face DOAR din CLI, pe masina gateway-ului (admin) — nu exista
|
|
|
|
|
> suprafata HTTP de administrare de securizat. Cheia in clar se afiseaza **o singura data**
|
|
|
|
|
> la creare/rotire; in DB se pastreaza doar hash-ul SHA-256.
|
|
|
|
|
|
|
|
|
|
### Creare cont + cheie pentru un service nou
|
|
|
|
|
|
|
|
|
|
Pana la onboarding-ul web, un cont nou se creeaza direct in DB, apoi i se emite o cheie:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 1. Creeaza contul (numele + CUI sunt informative)
|
|
|
|
|
python3 -c "
|
|
|
|
|
from app.db import get_connection, init_db
|
|
|
|
|
init_db()
|
|
|
|
|
c = get_connection()
|
|
|
|
|
cur = c.execute(\"INSERT INTO accounts (name, cui) VALUES ('Service Auto SRL', 'RO12345678')\")
|
|
|
|
|
print('account_id nou =', cur.lastrowid); c.commit(); c.close()
|
|
|
|
|
"
|
|
|
|
|
|
|
|
|
|
# 2. Emite o cheie API pentru cont (afisata O SINGURA DATA)
|
|
|
|
|
python3 -m tools.apikey create --account 2
|
|
|
|
|
# -> rfak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
|
|
|
|
|
|
|
|
# Alte operatii
|
|
|
|
|
python3 -m tools.apikey list # toate cheile
|
|
|
|
|
python3 -m tools.apikey list --account 2 # cheile unui cont
|
|
|
|
|
python3 -m tools.apikey rotate --account 2 # revoca cele active + emite una noua
|
|
|
|
|
python3 -m tools.apikey revoke --key-id 3 # revoca o cheie dupa id
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Creds RAR per cont
|
|
|
|
|
|
|
|
|
|
Ca worker-ul sa poata trimite pentru un service fara ca fiecare cerere sa-i poarte parola
|
|
|
|
|
RAR, seteaza credentialele RAR durabile pe cont (criptate Fernet at-rest):
|
|
|
|
|
**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 \
|
|
|
|
|
@@ -290,126 +113,66 @@ curl -s -X POST http://localhost:8010/v1/conturi/rar-creds \
|
|
|
|
|
-d '{"email": "service@exemplu.ro", "password": "parola-rar"}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Testare prin API (curl)
|
|
|
|
|
> GET-urile de listare (`/v1/prezentari`, `/v1/nomenclator`, `/v1/audit/export`) sunt momentan
|
|
|
|
|
> **globale si neprotejate** — filtrarea pe cont ramane de adaugat.
|
|
|
|
|
|
|
|
|
|
Exemplele de mai jos arata atat varianta **dev** (fara cheie -> cont id=1), cat si varianta
|
|
|
|
|
**service cu cheie API** (header `X-API-Key`). Cand `AUTOPASS_REQUIRE_API_KEY=true`, cheia e
|
|
|
|
|
obligatorie.
|
|
|
|
|
## Proba reala la RAR (mediu test)
|
|
|
|
|
|
|
|
|
|
1. Pune creds de test in `settings.xml` (copiaza din `settings.xml.example`, bloc `<test>`; **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
|
|
|
|
|
# Sanatate (neprotejat)
|
|
|
|
|
curl -s http://localhost:8010/healthz | python3 -m json.tool
|
|
|
|
|
|
|
|
|
|
# Nomenclator RAR (cache local)
|
|
|
|
|
curl -s http://localhost:8010/v1/nomenclator
|
|
|
|
|
|
|
|
|
|
# Coada de prezentari (monitorizare; momentan globala + neprotejata, vezi nota de mai sus)
|
|
|
|
|
curl -s http://localhost:8010/v1/prezentari
|
|
|
|
|
|
|
|
|
|
# Trimite o prezentare -- dev (fara cheie API -> cont id=1)
|
|
|
|
|
curl -s -X POST http://localhost:8010/v1/prezentari \
|
|
|
|
|
-H 'Content-Type: application/json' \
|
|
|
|
|
-d '{
|
|
|
|
|
"rar_credentials": {"email": "test@example.ro", "password": "secret"},
|
|
|
|
|
"prezentari": [{
|
|
|
|
|
"vin": "WAUZZZ8K0AA000001",
|
|
|
|
|
"nr_inmatriculare": "B123ABC",
|
|
|
|
|
"data_prestatie": "2026-06-15",
|
|
|
|
|
"odometru_final": "120000",
|
|
|
|
|
"prestatii": [{"cod_op_service": "REVIZIE PERIODICA", "denumire": "REVIZIE PERIODICA"}]
|
|
|
|
|
}]
|
|
|
|
|
}'
|
|
|
|
|
|
|
|
|
|
# Trimite o prezentare -- service cu cheie API (account_id curge din cheie)
|
|
|
|
|
curl -s -X POST http://localhost:8010/v1/prezentari \
|
|
|
|
|
-H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
|
|
|
|
|
-d '{
|
|
|
|
|
"rar_credentials": {"email": "service@exemplu.ro", "password": "parola-rar"},
|
|
|
|
|
"prezentari": [{
|
|
|
|
|
"vin": "WAUZZZ8K0AA000002",
|
|
|
|
|
"nr_inmatriculare": "B456DEF",
|
|
|
|
|
"data_prestatie": "2026-06-16",
|
|
|
|
|
"odometru_final": "85000",
|
|
|
|
|
"prestatii": [{"cod_op_service": "REPARATIE", "denumire": "REPARATIE"}]
|
|
|
|
|
}]
|
|
|
|
|
}'
|
|
|
|
|
|
|
|
|
|
# Import fisier prin API pentru un service (multi-tenant: contul vine din cheie)
|
|
|
|
|
curl -s -X POST http://localhost:8010/v1/import \
|
|
|
|
|
-H 'X-API-Key: rfak_...' -F 'file=@sample_import.xlsx'
|
|
|
|
|
python3 -m pytest -q # toata suita
|
|
|
|
|
python3 -m pytest tests/test_x.py -q # un fisier
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Endpointurile complete sunt vizibile si testabile in `/docs` (Swagger UI). In Swagger,
|
|
|
|
|
pune cheia prin butonul "Authorize" sau adauga header-ul `X-API-Key`.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Sanatate
|
|
|
|
|
curl -s http://localhost:8010/healthz | python3 -m json.tool
|
|
|
|
|
|
|
|
|
|
# Nomenclator RAR (cache local)
|
|
|
|
|
curl -s http://localhost:8010/v1/nomenclator
|
|
|
|
|
|
|
|
|
|
# Coada de prezentari
|
|
|
|
|
curl -s http://localhost:8010/v1/prezentari
|
|
|
|
|
|
|
|
|
|
# Trimite o prezentare (dev: fara cheie API -> cont id=1)
|
|
|
|
|
curl -s -X POST http://localhost:8010/v1/prezentari \
|
|
|
|
|
-H 'Content-Type: application/json' \
|
|
|
|
|
-d '{
|
|
|
|
|
"rar_credentials": {"email": "test@example.ro", "password": "secret"},
|
|
|
|
|
"prezentari": [{
|
|
|
|
|
"vin": "WAUZZZ8K0AA000001",
|
|
|
|
|
"nr_inmatriculare": "B123ABC",
|
|
|
|
|
"data_prestatie": "2026-06-15",
|
|
|
|
|
"odometru_final": "120000",
|
|
|
|
|
"prestatii": [{"cod_op_service": "REVIZIE PERIODICA", "denumire": "REVIZIE PERIODICA"}]
|
|
|
|
|
}]
|
|
|
|
|
}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Endpointurile complete sunt vizibile si testabile in `/docs` (Swagger UI).
|
|
|
|
|
|
|
|
|
|
## Rularea testelor
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
python3 -m pytest -q
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Suita acopera fundatia, securitatea, validarea, parserul de import, masina de stari a
|
|
|
|
|
worker-ului si fluxul UI de import (E2E cu RAR mock).
|
|
|
|
|
|
|
|
|
|
## Docker / deploy
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 1. Pregateste .env (CRITIC: AUTOPASS_CREDS_KEY partajata intre api si worker)
|
|
|
|
|
cp .env.example .env
|
|
|
|
|
# completeaza AUTOPASS_CREDS_KEY (vezi comanda de generare de mai sus)
|
|
|
|
|
|
|
|
|
|
# 2. Porneste API + worker + autoheal
|
|
|
|
|
docker compose up --build
|
|
|
|
|
cp .env.example .env # CRITIC: completeaza AUTOPASS_CREDS_KEY (partajata api+worker)
|
|
|
|
|
docker compose up --build # api (:8010) + worker + autoheal, acelasi image + volum SQLite
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`docker-compose.yml` porneste trei containere: `api` (port 8010), `worker` si `autoheal`
|
|
|
|
|
(restarteaza worker-ul cand heartbeat-ul devine invechit). Ambele servicii folosesc acelasi
|
|
|
|
|
image si acelasi volum SQLite persistent.
|
|
|
|
|
|
|
|
|
|
## Structura
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
app/
|
|
|
|
|
main.py # FastAPI: API v1 + dashboard + /healthz + /metrics
|
|
|
|
|
api/v1/ # router.py (prezentari, nomenclator, mapari) + import_router.py
|
|
|
|
|
web/ # routes.py (dashboard + import UI HTMX) + templates/ + static/
|
|
|
|
|
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)
|
|
|
|
|
validation.py # validare continut (T3)
|
|
|
|
|
mapping.py # mapare operatie -> cod prestatie + fuzzy lookup
|
|
|
|
|
crypto.py # criptare Fernet creds RAR efemere (zero-storage at rest)
|
|
|
|
|
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
|
|
|
|
|
docs/ # contract RAR (sursa de adevar) + ROADMAP (progres + proces)
|
|
|
|
|
tests/ # suita pytest
|
|
|
|
|
legacy-vfp/ # arhiva Visual FoxPro ROAAUTO (legacy, doar referinta/migrare)
|
|
|
|
|
tools/ # CLI admin: account, apikey, backup, rar_finalizate, import_dbf
|
|
|
|
|
docs/ # contract RAR + ROADMAP + prd/
|
|
|
|
|
tests/ legacy-vfp/ # suita pytest · arhiva ROAAUTO (referinta)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Contract RAR (sursa de adevar): [`docs/api-rar-contract.md`](docs/api-rar-contract.md).
|
|
|
|
|
Roadmap + proces de dezvoltare: [`docs/ROADMAP.md`](docs/ROADMAP.md).
|
|
|
|
|
|