STABLE: Final schema sync, optimized gitignore

This commit is contained in:
Kincses
2026-02-26 08:19:25 +01:00
parent 893f39fa15
commit 505543330a
203 changed files with 11590 additions and 9542 deletions

View File

@@ -1,40 +1,123 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional, Any
from datetime import datetime
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/admin.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, text, delete
from typing import List, Any, Dict, Optional
from datetime import datetime, timedelta
# --- Pontszabályok (Point Rules) ---
class PointRuleBase(BaseModel):
rule_key: str
points: int
region_code: str = "GLOBAL"
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
is_active: bool = True
from app.api import deps
from app.models.identity import User, UserRole
from app.models.system import SystemParameter
from app.models.audit import SecurityAuditLog, OperationalLog
from app.models.security import PendingAction, ActionStatus
from app.services.security_service import security_service
from app.services.translation_service import TranslationService
from app.schemas.admin import PointRuleResponse, LevelConfigResponse, ConfigUpdate
from app.schemas.admin_security import PendingActionResponse, SecurityStatusResponse
class PointRuleCreate(PointRuleBase):
pass
router = APIRouter()
class PointRuleResponse(PointRuleBase):
id: int
model_config = ConfigDict(from_attributes=True)
# --- 🛡️ ADMIN JOGOSULTSÁG ELLENŐRZŐ ---
async def check_admin_access(current_user: User = Depends(deps.get_current_active_user)):
""" Csak Admin vagy Superadmin léphet be a Sentinel központba. """
if current_user.role not in [UserRole.admin, UserRole.superadmin]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Sentinel jogosultság szükséges a művelethez!"
)
return current_user
# --- Regionális Beállítások (MOT, Tax, stb.) ---
class RegionalSettingBase(BaseModel):
region_code: str
setting_key: str
value: Any # JSON adat (pl. {"months": 24})
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
# --- 🛰️ 1. SENTINEL: RENDSZERÁLLAPOT ÉS MONITORING ---
class RegionalSettingCreate(RegionalSettingBase):
pass
@router.get("/health-monitor", response_model=Dict[str, Any], tags=["Sentinel Monitoring"])
async def get_system_health(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Részletes rendszerstatisztikák (Felhasználók, Eszközök, Biztonság). """
stats = {}
# Felhasználói eloszlás (Nyers SQL a sebességért)
user_res = await db.execute(text("SELECT subscription_plan, count(*) FROM data.users GROUP BY subscription_plan"))
stats["user_distribution"] = {row[0]: row[1] for row in user_res}
# Eszköz és Szervezet számlálók
stats["total_assets"] = (await db.execute(text("SELECT count(*) FROM data.assets"))).scalar()
stats["total_organizations"] = (await db.execute(text("SELECT count(*) FROM data.organizations"))).scalar()
# --- Szintlépési Konfiguráció ---
class LevelConfigBase(BaseModel):
level_number: int
min_points: int
name_translation_key: str
region_code: str = "GLOBAL"
# Biztonsági riasztások (Kritikus logok az elmúlt 24 órában)
day_ago = datetime.now() - timedelta(days=1)
crit_logs = await db.execute(
select(func.count(SecurityAuditLog.id))
.where(SecurityAuditLog.is_critical == True, SecurityAuditLog.created_at >= day_ago)
)
stats["critical_alerts_24h"] = crit_logs.scalar() or 0
class LevelConfigUpdate(LevelConfigBase):
pass
return stats
# --- ⚖️ 2. SENTINEL: NÉGY SZEM ELV (Approval System) ---
@router.get("/pending-actions", response_model=List[PendingActionResponse], tags=["Sentinel Security"])
async def list_pending_actions(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Jóváhagyásra váró kritikus műveletek listázása. """
stmt = select(PendingAction).where(PendingAction.status == ActionStatus.pending)
result = await db.execute(stmt)
return result.scalars().all()
@router.post("/approve/{action_id}", tags=["Sentinel Security"])
async def approve_action(
action_id: int,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Művelet véglegesítése egy második admin által. """
try:
await security_service.approve_action(db, admin.id, action_id)
return {"status": "success", "message": "Művelet végrehajtva."}
except Exception as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# --- ⚙️ 3. DINAMIKUS KONFIGURÁCIÓ (System Parameters) ---
@router.get("/parameters", tags=["Dynamic Configuration"])
async def list_all_parameters(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Globális és lokális paraméterek (Limitek, XP szorzók) lekérése. """
result = await db.execute(select(SystemParameter))
return result.scalars().all()
@router.post("/parameters", tags=["Dynamic Configuration"])
async def set_parameter(
config: ConfigUpdate,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Paraméter beállítása vagy frissítése hierarchikus scope-al. """
query = text("""
INSERT INTO data.system_parameters (key, value, scope_level, scope_id, category, last_modified_by)
VALUES (:key, :val, :sl, :sid, :cat, :user)
ON CONFLICT (key, scope_level, scope_id)
DO UPDATE SET
value = EXCLUDED.value,
category = EXCLUDED.category,
last_modified_by = EXCLUDED.last_modified_by,
updated_at = now()
""")
await db.execute(query, {
"key": config.key, "val": config.value, "sl": config.scope_level,
"sid": config.scope_id, "cat": config.category, "user": admin.email
})
await db.commit()
return {"status": "success", "message": f"'{config.key}' frissítve."}
@router.post("/translations/sync", tags=["System Utilities"])
async def sync_translations(db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
""" DB fordítások exportálása JSON fájlokba a frontendnek. """
await TranslationService.export_to_json(db)
return {"message": "Nyelvi fájlok frissítve."}

View File

@@ -1,6 +1,7 @@
from pydantic import BaseModel
# /opt/docker/dev/service_finder/backend/app/schemas/admin_security.py
from pydantic import BaseModel, ConfigDict
from datetime import datetime
from typing import Optional, Any, Dict, List
from typing import Optional, Any, Dict
from app.models.security import ActionStatus
class PendingActionResponse(BaseModel):
@@ -13,14 +14,10 @@ class PendingActionResponse(BaseModel):
created_at: datetime
expires_at: datetime
class Config:
from_attributes = True
class ActionApproveRequest(BaseModel):
# Itt akár extra jelszót vagy MFA tokent is kérhetnénk a jövőben
comment: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class SecurityStatusResponse(BaseModel):
total_pending: int
critical_logs_last_24h: int
emergency_locks_active: int
emergency_locks_active: int

View File

@@ -1,73 +1,56 @@
# /opt/docker/dev/service_finder/backend/app/schemas/asset.py
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional, Dict, Any, List
from uuid import UUID
from datetime import datetime
# --- KATALÓGUS SÉMÁK (Gyári adatok) ---
class AssetCatalogBase(BaseModel):
"""Alap katalógus adatok, amik a technikai dúsításból származnak."""
class AssetCatalogResponse(BaseModel):
""" A technikai katalógus (Master Data) teljes adattartalma. """
id: int
make: str
model: str
generation: Optional[str] = None
engine_variant: Optional[str] = None
year_from: Optional[int] = None
year_to: Optional[int] = None
vehicle_class: Optional[str] = None
fuel_type: Optional[str] = None
engine_code: Optional[str] = None
# --- ÚJ TECHNIKAI MEZŐK (Robot v1.0.8 Smart Hunter adatai) ---
# Technikai paraméterek az automatizáláshoz
power_kw: Optional[int] = None
engine_capacity: Optional[int] = None
max_weight_kg: Optional[int] = None
axle_count: Optional[int] = None
euro_class: Optional[str] = None
body_type: Optional[str] = None
class AssetCatalogResponse(AssetCatalogBase):
"""Katalógus válasz séma azonosítóval és extra gyári adatokkal."""
id: int
factory_data: Optional[Dict[str, Any]] = None
engine_code: Optional[str] = None
factory_data: Dict[str, Any] = Field(default_factory=dict)
# Pydantic v2 konfiguráció az ORM (SQLAlchemy) támogatáshoz
model_config = ConfigDict(from_attributes=True)
# --- JÁRMŰ SÉMÁK (Asset) ---
class AssetBase(BaseModel):
"""Jármű példány alapadatai (egyedi azonosítók)."""
class AssetResponse(BaseModel):
""" A konkrét járműpéldány (Asset) teljes válaszmodellje. """
id: UUID
vin: str = Field(..., min_length=17, max_length=17)
license_plate: str
license_plate: Optional[str] = None
name: Optional[str] = None
year_of_manufacture: Optional[int] = None
class AssetCreate(AssetBase):
"""Séma új jármű felvételéhez."""
make: str
model: str
vehicle_class: Optional[str] = "car"
fuel_type: Optional[str] = None
current_reading: Optional[int] = 0
class AssetResponse(AssetBase):
"""
Teljes jármű válasz séma.
Ez a séma tartalmazza a 'catalog' objektumot, amiben a dúsított műszaki adatok vannak.
"""
id: UUID
catalog_id: int
catalog: AssetCatalogResponse # Ez a pont kapcsolja össze a dúsított technikai adatokat
# Státusz és ellenőrzés
status: str
is_verified: bool
verification_method: Optional[str] = None
catalog_match_score: Optional[float] = None
# Kapcsolt adatok
catalog_id: Optional[int] = None
catalog: Optional[AssetCatalogResponse] = None # Itt jön a dúsítás!
owner_organization_id: Optional[int] = None
operator_person_id: Optional[int] = None
created_at: datetime
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
# --- DIGITÁLIS IKER (Full Profile) ---
class AssetFullProfile(BaseModel):
"""
Komplex jelentésekhez használt séma.
Összefogja az identitást, telemetriát, pénzügyeket és szerviztörténetet.
"""
identity: Dict[str, Any]
telemetry: Dict[str, Any]
financial_summary: Dict[str, Any]
service_history: List[Dict[str, Any]]
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,35 +1,35 @@
from pydantic import BaseModel, Field
# /opt/docker/dev/service_finder/backend/app/schemas/asset_cost.py
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional, Dict, Any
from datetime import datetime
from decimal import Decimal
from uuid import UUID
class AssetCostBase(BaseModel):
"""Alap költség adatok (Frontendről érkező bevitel)."""
cost_type: str = Field(..., description="fuel, service, fine, insurance, toll, etc.")
amount_local: Decimal = Field(..., description="A fizetett bruttó összeg helyi devizában")
currency_local: str = Field("HUF", min_length=3, max_length=3)
date: datetime = Field(default_factory=datetime.now)
mileage_at_cost: Optional[int] = Field(None, description="Kilométeróra állása a költség rögzítésekor")
description: Optional[str] = None
cost_type: str # fuel, service, tax, insurance
amount_local: Decimal
currency_local: str = "HUF"
net_amount_local: Optional[Decimal] = None
vat_rate: Optional[Decimal] = Field(27.0, description="ÁFA kulcs (pl. 27.0)")
data: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Extra adatok (pl. helyszín, számlaszám)")
vat_rate: Optional[Decimal] = Field(default=27.0)
date: datetime = Field(default_factory=datetime.now)
mileage_at_cost: Optional[int] = None
description: Optional[str] = None
data: Dict[str, Any] = Field(default_factory=dict) # nyugta adatai, GPS koordináták
class AssetCostCreate(AssetCostBase):
"""Költség rögzítésekor használt séma."""
asset_id: UUID
organization_id: int
class AssetCostResponse(AssetCostBase):
"""Visszatérő adat modell a frontend felé."""
id: UUID
asset_id: UUID
organization_id: int
driver_id: Optional[int]
amount_eur: Decimal
exchange_rate_used: Decimal
created_at: Optional[datetime] = None
class Config:
from_attributes = True
driver_id: Optional[int] = None
# Pénzügyi dúsítás (Backend számolja)
amount_eur: Optional[Decimal] = None
exchange_rate_used: Optional[Decimal] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,72 +1,54 @@
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, Dict, Any
from datetime import date
# --- STEP 1: LITE REGISTRATION ---
class UserLiteRegister(BaseModel):
email: EmailStr
password: str = Field(..., min_length=8)
first_name: str
last_name: str
region_code: str = "HU"
lang: str = Field("hu", description="Választott nyelv kódja")
timezone: str = Field("Europe/Budapest", description="Felhasználó időzónája")
class UserLogin(BaseModel):
email: EmailStr
password: str
# --- STEP 2: KYC & ONBOARDING ---
class ICEContact(BaseModel):
name: str
phone: str
relationship: Optional[str] = None
# /opt/docker/dev/service_finder/backend/app/schemas/auth.py
from pydantic import BaseModel, EmailStr, Field, ConfigDict
from typing import Optional, Dict, List
from datetime import date, datetime
class DocumentDetail(BaseModel):
number: str
expiry_date: date
class ICEContact(BaseModel):
name: str
phone: str
relationship: str
class UserLiteRegister(BaseModel):
""" Step 1: Gyors regisztráció (Alap azonosítás). """
email: EmailStr
password: str = Field(..., min_length=8, description="Minimum 8 karakter hosszú jelszó")
first_name: str
last_name: str
model_config = ConfigDict(from_attributes=True)
class UserKYCComplete(BaseModel):
phone_number: str
""" Step 2: Teljes körű személyazonosítás és címadatok. """
phone_number: str = Field(..., pattern=r"^\+?[0-9]{7,15}$")
birth_place: str
birth_date: date
mothers_last_name: str
mothers_first_name: str
# Bontott címmezők (B pont szerint)
# Atomizált címadatok a pontos GPS-hez és Robot-munkához
address_zip: str
address_city: str
address_street_name: str
address_street_type: str
address_street_type: str # utca, út, tér...
address_house_number: str
address_stairwell: Optional[str] = None # Lépcsőház
address_floor: Optional[str] = None # Emelet
address_door: Optional[str] = None # Ajtó
address_hrsz: Optional[str] = None # Helyrajzi szám
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
address_hrsz: Optional[str] = None # Külterület/Helyrajzi szám
identity_docs: Dict[str, DocumentDetail]
# Okmányok és Vészhelyzet
identity_docs: Dict[str, DocumentDetail] # pl: {"ID_CARD": {...}, "LICENSE": {...}}
ice_contact: ICEContact
preferred_currency: Optional[str] = Field("HUF", max_length=3)
# --- COMMON & SECURITY ---
class PasswordResetRequest(BaseModel):
email: EmailStr
class PasswordResetConfirm(BaseModel):
email: EmailStr
token: str
password: str = Field(..., min_length=8)
password_confirm: str = Field(..., min_length=8)
preferred_language: str = "hu"
preferred_currency: str = "HUF"
class Token(BaseModel):
access_token: str
token_type: str
is_active: bool
class TokenPayload(BaseModel):
"""JWT Token payload struktúrája validációhoz."""
sub: Optional[str] = None
role: Optional[str] = None
rank: Optional[int] = 0
scope_level: Optional[str] = None
scope_id: Optional[str] = None
region: Optional[str] = None
refresh_token: Optional[str] = None
token_type: str = "bearer"
is_active: bool

View File

@@ -1,56 +1,20 @@
# /opt/docker/dev/service_finder/backend/app/schemas/fleet.py
from pydantic import BaseModel, ConfigDict
from typing import Optional, List
from datetime import date, datetime
from app.models.expense import ExpenseCategory
from datetime import date
from uuid import UUID
# --- Vehicle Schemas ---
class VehicleBase(BaseModel):
license_plate: str
make: str
model: str
year: int
fuel_type: Optional[str] = None
vin: Optional[str] = None
initial_odometer: int = 0
mot_expiry_date: Optional[date] = None
insurance_expiry_date: Optional[date] = None
class VehicleCreate(VehicleBase):
pass
class VehicleResponse(VehicleBase):
id: int
current_odometer: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)
# --- Event / Expense Schemas ---
class EventBase(BaseModel):
event_type: ExpenseCategory
class EventCreate(BaseModel):
asset_id: UUID
event_type: str # 'SERVICE', 'FUEL', 'MOT'
date: date
odometer_value: int
cost_amount: int
cost_amount: float
description: Optional[str] = None
is_diy: bool = False
# Ad-Hoc Provider mező: Ha stringet kapunk, a service megkeresi vagy létrehozza
provider_name: Optional[str] = None
provider_id: Optional[int] = None # Ha már ismert ID-t küldünk
class EventCreate(EventBase):
pass
class EventResponse(EventBase):
id: int
vehicle_id: int
odometer_anomaly: bool
service_provider_id: Optional[int]
image_paths: Optional[List[str]] = []
model_config = ConfigDict(from_attributes=True)
provider_id: Optional[int] = None
class TCOStats(BaseModel):
vehicle_id: int
total_cost: int
breakdown: dict[str, int] # Kategóriánkénti bontás
cost_per_km: Optional[float] = 0.0
asset_id: UUID
total_cost_huf: float
cost_per_km: float
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,4 +1,4 @@
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
class ContactCreate(BaseModel):
@@ -8,30 +8,31 @@ class ContactCreate(BaseModel):
contact_type: str = "primary"
class CorpOnboardIn(BaseModel):
# Névkezelés
full_name: str = Field(..., description="Teljes hivatalos név")
name: str = Field(..., description="Rövidített cégnév (pl. ProfiBot Kft.)")
display_name: str = Field(..., description="Alkalmazáson belüli rövidítés (pl. ProfiBot)")
""" Teljes onboarding adatcsomag atomizált címekkel. """
full_name: str = Field(..., description="Hivatalos cégnév")
name: str = Field(..., description="Rövid név")
display_name: str
tax_number: str
country_code: str = "HU"
language: str = Field("hu", description="A szervezet alapértelmezett nyelve")
default_currency: str = Field("HUF", description="A szervezet alapértelmezett pénzneme")
reg_number: Optional[str] = None
country_code: str = "HU"
language: str = "hu"
default_currency: str = "HUF"
# Atomizált Címkezelés
# --- ATOMIZÁLT CÍM (Modell szinkron) ---
address_zip: str
address_city: str
address_street_name: Optional[str] = None
address_street_type: Optional[str] = None # utca, út, tér, dűlő
address_house_number: Optional[str] = None
address_hrsz: Optional[str] = None # Helyrajzi szám (ha nincs utca/házszám)
address_street_name: str
address_street_type: str
address_house_number: str
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
address_hrsz: Optional[str] = None
contacts: List[ContactCreate] = []
class CorpOnboardResponse(BaseModel):
organization_id: int
status: str
status: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,45 +1,38 @@
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
class ServiceCreateInternal(BaseModel):
name: str = Field(..., description="A szolgáltató neve")
# --- HIERARCHIA ---
# Ha a robot felismeri, hogy egy lánc része, itt tároljuk a szülő ID-t
parent_id: Optional[int] = Field(None, description="Szülő egység ID-ja (pl. Franchise központ)")
# --- CÍM ADATOK ---
postal_code: Optional[str] = None
city: str
street_name: Optional[str] = None
street_type: Optional[str] = "utca"
house_number: Optional[str] = None
stairwell: Optional[str] = None
floor: Optional[str] = None
door: Optional[str] = None
hrsz: Optional[str] = None
full_address: Optional[str] = Field(None, description="Eredeti, nyers cím szövege")
# --- ELÉRHETŐSÉG ---
contact_phone: Optional[str] = None
email: Optional[str] = None
website: Optional[str] = None
# --- SOCIAL & AI ---
# A Deep Dive fázishoz előkészítve
social_links: Optional[Dict[str, str]] = Field(default_factory=dict)
vibe_analysis: Optional[Dict[str, Any]] = Field(default_factory=dict)
# --- IDENTITÁS ÉS FORRÁS ---
source: str # 'google', 'osm', 'manual', 'fb_scraper'
external_id: Optional[str] = None
# Ez a robot "horgonya" a duplikációk ellen
fingerprint: str = Field(..., description="Egyedi ujjlenyomat: Hash(Name+City+Street)")
trust_score: int = Field(30, ge=0, le=100)
raw_data: Optional[Dict[str, Any]] = {}
class ContactCreate(BaseModel):
full_name: str
email: str
phone: Optional[str] = None
contact_type: str = "primary"
class Config:
from_attributes = True
class CorpOnboardIn(BaseModel):
""" Teljes onboarding adatcsomag atomizált címekkel. """
full_name: str = Field(..., description="Hivatalos cégnév")
name: str = Field(..., description="Rövid név")
display_name: str
tax_number: str
reg_number: Optional[str] = None
country_code: str = "HU"
language: str = "hu"
default_currency: str = "HUF"
# --- ATOMIZÁLT CÍM (Modell szinkron) ---
address_zip: str
address_city: str
address_street_name: str
address_street_type: str
address_house_number: str
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
address_hrsz: Optional[str] = None
contacts: List[ContactCreate] = []
class CorpOnboardResponse(BaseModel):
organization_id: int
status: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,12 +1,9 @@
from pydantic import BaseModel, Field
from typing import Optional, Dict
# /opt/docker/dev/service_finder/backend/app/schemas/service_hunt.py
class ServiceHuntRequest(BaseModel):
name: str = Field(..., example="Kovács Autóvillamosság")
name: str
category_id: int
address: str
latitude: float # A szerviz koordinátája
latitude: float
longitude: float
user_latitude: float # A felhasználó aktuális helyzete (GPS-ből)
user_longitude: float
name_translations: Optional[Dict[str, str]] = None
user_latitude: float
user_longitude: float

View File

@@ -1,9 +1,10 @@
# /opt/docker/dev/service_finder/backend/app/schemas/social.py
from pydantic import BaseModel, ConfigDict
from typing import Optional, List
from datetime import datetime
from app.models.social import ModerationStatus, SourceType
# --- Alap Sémák ---
# --- Alap Sémák (Szolgáltatók) ---
class ServiceProviderBase(BaseModel):
name: str
@@ -19,42 +20,39 @@ class ServiceProviderCreate(BaseModel):
class ServiceProviderResponse(ServiceProviderBase):
id: int
status: ModerationStatus
validation_score: int # Látni kell a pontszámot
validation_score: int
evidence_image_path: Optional[str] = None
added_by_user_id: Optional[int] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
# --- Voting & Gamification Sémák ---
# --- Gamifikáció és Szavazás (Voting & Gamification) ---
class VoteCreate(BaseModel):
vote_value: int # Csak a +1 vagy -1 kell, a user_id jön a tokenből, a provider_id az URL-ből
vote_value: int
class LeaderboardEntry(BaseModel):
username: str
points: int
rank: int
model_config = ConfigDict(from_attributes=True)
# --- GAMIFIKÁCIÓS SÉMÁK (Amiket a log keresett) ---
class BadgeSchema(BaseModel):
id: int
name: str
description: str
image_url: Optional[str] = None
class Config:
from_attributes = True
icon_url: Optional[str] = None # JAVÍTVA: icon_url a modell szerint
model_config = ConfigDict(from_attributes=True) # Pydantic V2 kompatibilis
class UserStatSchema(BaseModel):
user_id: int
total_points: int
total_xp: int # JAVÍTVA: total_xp a modell szerint
current_level: int
rank_title: str
penalty_points: int # JAVÍTVA: új mező
rank_title: Optional[str] = None
badges: List[BadgeSchema] = []
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,52 +1,25 @@
# /opt/docker/dev/service_finder/backend/app/schemas/user.py
from pydantic import BaseModel, EmailStr, field_validator, ConfigDict
from typing import Optional
from datetime import date
# Alap adatok, amik mindenhol kellenek
class UserBase(BaseModel):
email: EmailStr
first_name: Optional[str] = None
last_name: Optional[str] = None
is_active: Optional[bool] = True
is_superuser: bool = False
is_active: bool = True
region_code: str = "HU"
# --- REGISZTRÁCIÓ ---
class UserRegister(UserBase):
password: str
birthday: Optional[date] = None
is_company: bool = False
company_name: Optional[str] = None
tax_number: Optional[str] = None
@field_validator('email')
@classmethod
def block_temporary_emails(cls, v: str) -> str:
blacklist = ['mailinator.com', '10minutemail.com', 'temp-mail.org', 'guerrillamail.com']
domain = v.split('@')[-1].lower()
if domain in blacklist:
raise ValueError('Ideiglenes email szolgáltató nem engedélyezett!')
return v
@field_validator('tax_number')
@classmethod
def validate_tax_id(cls, v: Optional[str], info) -> Optional[str]:
if info.data.get('is_company') and (not v or len(v) < 8):
raise ValueError('Cég esetén az adószám első 8 karaktere kötelező!')
return v
# --- VÁLASZ (Ezt hiányolta a rendszer!) ---
class UserResponse(UserBase):
id: int
is_company: bool
company_name: Optional[str] = None
# Pydantic V2 konfiguráció az ORM (SQLAlchemy) támogatáshoz
person_id: Optional[int] = None
role: str
subscription_plan: str
scope_level: str
scope_id: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
# Frissítéshez használt séma
class UserUpdate(BaseModel):
password: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
email: Optional[EmailStr] = None
preferred_language: Optional[str] = None