180 lines
6.4 KiB
Markdown
180 lines
6.4 KiB
Markdown
# 🏗️ 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)
|