# 🏗️ Logic Spec: Identity-Preserving Soft Delete & Asset Validation ## 1. Modul Célja és Masterbook 2 Illeszkedés **Mérföldkő:** Identity & Security Hardening **Masterbook 2 Referencia:** [`docs/v201/05_AUTH_AND_IDENTITY_SPEC.md`](docs/v201/05_AUTH_AND_IDENTITY_SPEC.md) (Soft Delete / Anonymization szekció) **Masterbook 2 Referencia:** [`docs/v201/18_ASSET_AND_FLEET_SPECIFICATION.md`](docs/v201/18_ASSET_AND_FLEET_SPECIFICATION.md) (Asset specifikáció) Jelenlegi állapot a Masterbook szerint: - A Soft Delete már létezik, de **hiányos**: nincs `deleted_at` timestamp, nincs token invalidáció, nem védi a Person rekordot. - Az Asset modellben nincs `CheckConstraint` a VIN/Rendszám "Vagy-Vagy" szabályhoz. - A Pydantic sémákban nincs `model_validator` a kötelező azonosító validációhoz. --- ## 2. Adatmodell változások ### 2.1 Asset CheckConstraint (`backend/app/models/vehicle/asset.py`) **Jelenlegi állapot:** A [`Asset`](backend/app/models/vehicle/asset.py:69) osztály `__table_args__`-ja csak a sémát adja meg: ```python __table_args__ = {"schema": "vehicle"} ``` **Módosítás:** Bővítsük ki a `__table_args__`-t egy `CheckConstraint`-tel: ```python __table_args__ = ( CheckConstraint( "vin IS NOT NULL OR license_plate IS NOT NULL", name="ck_asset_vin_or_plate_required" ), {"schema": "vehicle"} ) ``` **Import:** A [`CheckConstraint`](backend/app/models/vehicle/asset.py:7) már elérhető az SQLAlchemy importok között? Ellenőrizendő: jelenleg csak `UniqueConstraint` van importálva. Ki kell egészíteni: ```python from sqlalchemy import String, Boolean, DateTime, ForeignKey, Numeric, text, Text, UniqueConstraint, CheckConstraint, BigInteger, Integer, Float ``` ### 2.2 deleted_at oszlop a User modellhez (`backend/app/models/identity/identity.py`) **Jelenlegi állapot:** A [`User`](backend/app/models/identity/identity.py:122) osztályban már van `is_deleted: Mapped[bool]` (149. sor), de hiányzik a `deleted_at`. **Módosítás:** Add hozzá a `deleted_at` mezőt a `User` osztályhoz a `created_at` után: ```python # === SOFT DELETE === deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) ``` ### 2.3 deleted_at oszlop a Person modellhez (opcionális, struktúra miatt) **Jelenlegi állapot:** A [`Person`](backend/app/models/identity/identity.py:36) osztályban nincs `deleted_at`. **Módosítás:** Add hozzá a struktúra konzisztencia miatt: ```python # === SOFT DELETE (structure only - never delete Person data) === deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) ``` --- ## 3. Pydantic Séma Módosítások ### 3.1 AssetCreate séma (`backend/app/schemas/asset.py`) **Jelenlegi állapot:** A [`license_plate`](backend/app/schemas/asset.py:116) kötelező (`Field(...)`), a `vin` opcionális. **Módosítás:** Mindkét mező legyen `Optional[str] = None`: ```python license_plate: Optional[str] = Field(None, min_length=2, max_length=20, description="Rendszám") vin: Optional[str] = Field(None, min_length=1, max_length=50, description="VIN szám (opcionális)") ``` **Validátor:** Adj hozzá egy `@model_validator` (Pydantic V2 mód) vagy `@root_validator` (Pydantic V1 mód) metódust, ami: 1. Üres string (`""`) átalakítása `None`-ra mindkét mezőnél 2. Ha mindkettő `None`, dobjon `ValueError`-t (422-es HTTP válasz) ### 3.2 AssetUpdate séma (`backend/app/schemas/asset.py`) **Jelenlegi állapot:** A [`license_plate`](backend/app/schemas/asset.py:203) és `vin` már `Optional[str] = None`. **Módosítás:** Adj hozzá egy `@model_validator`-t vagy `@root_validator`-t, ami csak akkor dob hibát, ha a felhasználó explicitly `None`-ra akarja állítani mindkettőt. --- ## 4. Backend Service Logika: Person-Preserving Soft Delete ### 4.1 AuthService.soft_delete_user módosítása (`backend/app/services/auth_service.py`) **Jelenlegi kód:** [`soft_delete_user`](backend/app/services/auth_service.py:486-503) **Módosítások sorrendben:** 1. **`deleted_at` beállítás:** ```python user.deleted_at = datetime.now(timezone.utc) ``` 2. **E-mail átírás** (már létezik, de pontosítva): ```python old_email = user.email timestamp = datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S') user.email = f"deleted_{user.id}_{timestamp}_{old_email}" ``` 3. **Person rekord érintetlenül hagyása** (CRITICAL - maradjon ahogy van, NE módosítsuk) 4. **Token invalidáció:** Töröljük az összes aktív Refresh Token-t 5. **Naplózás** bővítése `deleted_at`-tal --- ## 5. API Végpont: DELETE /api/v1/users/me ### 5.1 Új endpoint (`backend/app/api/v1/endpoints/users.py`) ```python @router.delete("/me", status_code=200) async def delete_my_account( reason: Optional[str] = Body(None, description="Törlés oka (opcionális)"), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """ Saját fiók soft-delete. - Anonimizálja az e-mail címet - is_active = False, is_deleted = True, deleted_at = timestamp - Person rekordot NEM bántja - Érvényteleníti az összes refresh token-t - Wallet és Gamification adatokat megőrzi (inaktív user miatt freeze) """ success = await AuthService.soft_delete_user( db=db, user_id=current_user.id, reason=reason or "user_requested_self_delete", actor_id=current_user.id ) if not success: raise HTTPException( status_code=400, detail="A felhasználó már törölve van." ) return {"status": "ok", "message": "Fiók sikeresen törölve."} ``` --- ## 6. Adatbázis Szinkronizáció A módosítások után futtatni kell a sync_engine-t: ```bash docker exec -it sf_api python -m app.scripts.sync_engine ``` --- ## 7. Tesztterv ### 7.1 Asset validáció tesztelése 1. Hozz létre járművet **csak VIN-nel** → sikeres 2. Hozz létre járművet **csak rendszámmal** → sikeres 3. Hozz létre járművet **mindkettővel** → sikeres 4. Hozz létre járművet **egyik nélkül sem** → 422-es hiba ### 7.2 Soft Delete tesztelése 1. Hozz létre usert, authentikálj 2. DELETE `/api/v1/users/me` hívás 3. Ellenőrizd: `User.is_deleted = True`, `User.is_active = False`, `User.deleted_at` beállítva 4. Ellenőrizd: `Person` rekord adatai változatlanok, `Person.is_active` változatlan 5. Ellenőrizd: Refresh token törölve (régi session nem működik)