feat: schelet gateway FastAPI (API v1 + worker + dashboard + SQLite WAL)
Structura repo conform plan.md sect. 4, booteaza cu /healthz verde:
- app/main.py: FastAPI (lifespan init_db), /healthz (worker viu + last login + queue), /metrics
- app/api/v1: POST /v1/prezentari (enqueue + dedup idempotency UNIQUE), GET prezentari/{id}, nomenclator, mapari
- app/rar_client.py: client RAR real (login/JWT, nomenclator, postPrezentare, getFinalizate) cu User-Agent obligatoriu (fix WAF 403)
- app/worker: proces separat, claim atomic BEGIN IMMEDIATE, heartbeat, login+send (send dezactivat by default)
- app/web: dashboard Jinja2+HTMX (coada, banner alerta blocate, worker viu/mort, stari empty)
- app/db.py + schema.sql: SQLite WAL, tabele accounts/api_keys/operations_mapping/nomenclator_rar/submissions/worker_heartbeat
- app/idempotency.py + payload.py: hash continut canonic + builder payload (status FINALIZATA, fara tipPrestatie)
- Dockerfile + docker-compose.yml (api+worker, volum SQLite persistent, restart:always)
- tools/import_dbf.py: stub T5
Verificat live: login prin rar_client OK (token 259), nomenclator 18 coduri, worker heartbeat -> /healthz worker_alive=True.
Ramas: T3 validare Pydantic, T4 snapshot payload, T2 reconciliere/retry worker, T5 import DBF, auth API-key, middleware redactare creds, criptare PII.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
54
app/models.py
Normal file
54
app/models.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Modele Pydantic pentru suprafata API.
|
||||
|
||||
ATENTIE: validarea completa (regex VIN ^[A-HJ-NPR-Z0-9]{17}$, nrInmatriculare,
|
||||
dataPrestatie ∈ [2024-12-01, azi] TZ Bucuresti, R-ODO/I-ODO -> odometruInitial
|
||||
obligatoriu, odometruInitial <= odometruFinal, normalizare strip/upper) este
|
||||
**T3** — aici sunt doar formele de baza. Vezi plan.md sect. 2 + roadmap T3.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class RarCredentials(BaseModel):
|
||||
"""Credentiale RAR per-cerere (vin de la ROAAUTO din Oracle). NU se stocheaza."""
|
||||
|
||||
email: str
|
||||
password: str
|
||||
|
||||
|
||||
class PrestatieItem(BaseModel):
|
||||
cod_prestatie: str = Field(..., description="cod din nomenclator RAR, ex. OE-1")
|
||||
|
||||
|
||||
class PrezentareIn(BaseModel):
|
||||
"""O prezentare de declarat la RAR (inainte de validarea T3)."""
|
||||
|
||||
vin: str
|
||||
nr_inmatriculare: str
|
||||
data_prestatie: str # YYYY-MM-DD; validare interval = T3
|
||||
odometru_final: str # string per contract
|
||||
odometru_initial: str | None = None
|
||||
prestatii: list[PrestatieItem]
|
||||
sistem_reparat: str = "null"
|
||||
obs: str | None = None
|
||||
b64_image: str | None = None
|
||||
|
||||
|
||||
class PrezentareRequest(BaseModel):
|
||||
"""Body pentru POST /v1/prezentari — una sau mai multe prezentari + creds RAR."""
|
||||
|
||||
rar_credentials: RarCredentials
|
||||
prezentari: list[PrezentareIn] = Field(..., min_length=1)
|
||||
|
||||
|
||||
class SubmissionResult(BaseModel):
|
||||
submission_id: int
|
||||
status: str
|
||||
id_prezentare: int | None = None
|
||||
deduped: bool = False # True daca idempotency a intors un submission existent
|
||||
|
||||
|
||||
class PrezentariResponse(BaseModel):
|
||||
results: list[SubmissionResult]
|
||||
Reference in New Issue
Block a user