STABLE: Final schema sync, optimized gitignore
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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."}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user