feat: SuperAdmin bootstrap, i18n sync fix and AssetAssignment ORM fix

- Fixed AttributeError in User model (added region_code, preferred_language)
- Fixed InvalidRequestError in AssetAssignment (added organization relationship)
- Configured STATIC_DIR for translation sync
- Applied Alembic migrations for user schema updates
This commit is contained in:
2026-02-10 21:01:58 +00:00
parent e255fea3a5
commit 425f598fa3
51 changed files with 1753 additions and 204 deletions

View File

@@ -11,8 +11,10 @@ from .address import Address, GeoPostalCode, GeoStreet, GeoStreetType
from .gamification import PointRule, LevelConfig, UserStats, Badge, UserBadge, Rating, PointsLedger
from .system_config import SystemParameter
from .document import Document
from .translation import Translation # <--- HOZZÁADVA
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
from .history import AuditLog, VehicleOwnership
from .security import PendingAction # <--- HOZZÁADVA
# Aliasok
Vehicle = Asset
@@ -26,7 +28,8 @@ __all__ = [
"AssetEvent", "AssetFinancials", "AssetTelemetry", "AssetReview", "ExchangeRate",
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "PointRule",
"LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger",
"SystemParameter", "Document", "SubscriptionTier", "OrganizationSubscription",
"SystemParameter", "Document", "Translation", "PendingAction", # <--- BŐVÍTVE
"SubscriptionTier", "OrganizationSubscription",
"CreditTransaction", "ServiceSpecialty", "AuditLog", "VehicleOwnership",
"Vehicle", "UserVehicle", "VehicleCatalog", "ServiceRecord"
]

View File

@@ -75,7 +75,9 @@ class AssetReview(Base):
criteria_scores = Column(JSON, server_default=text("'{}'::jsonb"))
comment = Column(Text)
created_at = Column(DateTime(timezone=True), server_default=func.now())
asset = relationship("Asset", back_populates="reviews")
user = relationship("User") # <--- JAVÍTÁS: Hozzáadva
class AssetAssignment(Base):
__tablename__ = "asset_assignments"
@@ -86,7 +88,9 @@ class AssetAssignment(Base):
assigned_at = Column(DateTime(timezone=True), server_default=func.now())
released_at = Column(DateTime(timezone=True), nullable=True)
status = Column(String(30), default="active")
asset = relationship("Asset", back_populates="assignments")
organization = relationship("Organization") # <--- KRITIKUS JAVÍTÁS: Ez okozta a login hibát
class AssetEvent(Base):
__tablename__ = "asset_events"
@@ -115,7 +119,10 @@ class AssetCost(Base):
date = Column(DateTime(timezone=True), server_default=func.now())
mileage_at_cost = Column(Integer)
data = Column(JSON, server_default=text("'{}'::jsonb"))
asset = relationship("Asset", back_populates="costs")
organization = relationship("Organization") # <--- JAVÍTÁS: Hozzáadva
driver = relationship("User") # <--- JAVÍTÁS: Hozzáadva
class ExchangeRate(Base):
__tablename__ = "exchange_rates"

View File

@@ -1,16 +1,15 @@
import uuid
from datetime import datetime
from typing import Optional, TYPE_CHECKING
from sqlalchemy import ForeignKey, String, Integer, DateTime, func, Boolean
from sqlalchemy import ForeignKey, String, Integer, DateTime, func, Boolean, Text, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from app.db.base_class import Base
# Típusvizsgálathoz a körkörös import elkerülése érdekében
if TYPE_CHECKING:
from app.models.identity import User
# Közös beállítás az összes táblához ebben a fájlban
SCHEMA_ARGS = {"schema": "data"}
class PointRule(Base):
@@ -30,39 +29,36 @@ class LevelConfig(Base):
min_points: Mapped[int] = mapped_column(Integer)
rank_name: Mapped[str] = mapped_column(String)
class RegionalSetting(Base):
__tablename__ = "regional_settings"
__table_args__ = SCHEMA_ARGS
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
country_code: Mapped[str] = mapped_column(String, unique=True)
currency: Mapped[str] = mapped_column(String, default="HUF")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
class PointsLedger(Base):
__tablename__ = "points_ledger"
__table_args__ = SCHEMA_ARGS
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.users.id"))
points: Mapped[int] = mapped_column(Integer)
points: Mapped[int] = mapped_column(Integer, default=0)
# JAVÍTÁS: Itt is server_default-ot használunk
penalty_change: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
reason: Mapped[str] = mapped_column(String)
created_at: Mapped[datetime] = mapped_column(DateTime, default=func.now())
# Kapcsolat a felhasználóhoz
user: Mapped["User"] = relationship("User")
class UserStats(Base):
__tablename__ = "user_stats"
__table_args__ = SCHEMA_ARGS
# user_id a PK, mert 1:1 kapcsolat a User-rel
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.users.id"), primary_key=True)
total_xp: Mapped[int] = mapped_column(Integer, default=0)
social_points: Mapped[int] = mapped_column(Integer, default=0)
current_level: Mapped[int] = mapped_column(Integer, default=1)
# --- BÜNTETŐ RENDSZER (Strike System) ---
# JAVÍTÁS: server_default hozzáadva, hogy a meglévő sorok is 0-t kapjanak
penalty_points: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
restriction_level: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
# EZ A JAVÍTÁS: A visszamutató kapcsolat definiálása
user: Mapped["User"] = relationship("User", back_populates="stats")
class Badge(Base):
__tablename__ = "badges"
__table_args__ = SCHEMA_ARGS
@@ -81,7 +77,7 @@ class UserBadge(Base):
user: Mapped["User"] = relationship("User")
class Rating(Base): # <--- Az új értékelési modell
class Rating(Base):
__tablename__ = "ratings"
__table_args__ = SCHEMA_ARGS
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)

View File

@@ -1,9 +1,16 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, JSON, Date, Text
import enum
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, JSON, Date, Text, Enum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from app.db.base_class import Base
class LogSeverity(str, enum.Enum):
info = "info" # Általános művelet (pl. profil megtekintés)
warning = "warning" # Gyanús, de nem biztosan káros (pl. 3 elrontott jelszó)
critical = "critical" # Súlyos művelet (pl. jelszóváltoztatás, export)
emergency = "emergency" # Azonnali beavatkozást igényel (pl. SuperAdmin módosítás)
class VehicleOwnership(Base):
__tablename__ = "vehicle_ownerships"
__table_args__ = {"schema": "data"}
@@ -20,11 +27,25 @@ class VehicleOwnership(Base):
class AuditLog(Base):
__tablename__ = "audit_logs"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
target_type = Column(String, index=True)
target_id = Column(String, index=True)
action = Column(String, nullable=False)
changes = Column(JSON, nullable=True)
timestamp = Column(DateTime(timezone=True), server_default=func.now())
user_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
severity = Column(Enum(LogSeverity), default=LogSeverity.info, nullable=False)
# Mi történt és min?
action = Column(String(100), nullable=False, index=True)
target_type = Column(String(50), index=True) # pl. "User", "Wallet", "Asset"
target_id = Column(String(50), index=True) # A cél rekord ID-ja
# Részletes adatok (JSONB formátum a rugalmasságért)
# A 'changes' helyett explicit old/new párost használunk a könnyebb visszaállításhoz
old_data = Column(JSON, nullable=True)
new_data = Column(JSON, nullable=True)
# Biztonsági nyomkövetés
ip_address = Column(String(45), index=True) # IPv6-ot is támogat
user_agent = Column(Text, nullable=True) # Böngésző/Eszköz információ
timestamp = Column(DateTime(timezone=True), server_default=func.now(), index=True)
user = relationship("User")

View File

@@ -7,12 +7,12 @@ from sqlalchemy.sql import func
from app.db.base_class import Base
class UserRole(str, enum.Enum):
superadmin = "superadmin"
admin = "admin"
user = "user"
service = "service"
fleet_manager = "fleet_manager"
driver = "driver"
superadmin = "superadmin" # Hozzáadva a biztonság kedvéért
class Person(Base):
__tablename__ = "persons"
@@ -24,16 +24,9 @@ class Person(Base):
last_name = Column(String, nullable=False)
first_name = Column(String, nullable=False)
mothers_last_name = Column(String, nullable=True)
mothers_first_name = Column(String, nullable=True)
birth_place = Column(String, nullable=True)
birth_date = Column(DateTime, nullable=True)
phone = Column(String, nullable=True)
identity_docs = Column(JSON, server_default=text("'{}'::jsonb"))
medical_emergency = Column(JSON, server_default=text("'{}'::jsonb"))
ice_contact = Column(JSON, server_default=text("'{}'::jsonb"))
is_active = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
@@ -49,27 +42,27 @@ class User(Base):
hashed_password = Column(String, nullable=True)
role = Column(Enum(UserRole), default=UserRole.user)
is_active = Column(Boolean, default=False)
region_code = Column(String, default="HU")
is_deleted = Column(Boolean, default=False)
person_id = Column(BigInteger, ForeignKey("data.persons.id"), nullable=True)
preferred_language = Column(String(5), default="hu")
preferred_currency = Column(String(3), default="HUF")
timezone = Column(String(50), default="Europe/Budapest")
# RBAC & SCOPE mezők (Visszaállítva a DB sémához)
# ÚJ MEZŐK HOZZÁADVA:
preferred_language = Column(String(5), server_default="hu")
region_code = Column(String(5), server_default="HU")
# RBAC & SCOPE
scope_level = Column(String(30), server_default="individual")
scope_id = Column(String(50))
custom_permissions = Column(JSON, server_default=text("'{}'::jsonb"))
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Kapcsolatok
person = relationship("Person", back_populates="users")
wallet = relationship("Wallet", back_populates="user", uselist=False)
stats = relationship("UserStats", back_populates="user", uselist=False)
ownership_history = relationship("VehicleOwnership", back_populates="user")
owned_organizations = relationship("Organization", back_populates="owner")
# A Wallet és VerificationToken osztályok maradnak változatlanok...
class Wallet(Base):
__tablename__ = "wallets"
__table_args__ = {"schema": "data"}

View File

@@ -0,0 +1,44 @@
import enum
import uuid
from datetime import datetime, timedelta
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, JSON, Enum, text
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.db.base_class import Base
class ActionStatus(str, enum.Enum):
pending = "pending" # Jóváhagyásra vár
approved = "approved" # Végrehajtva
rejected = "rejected" # Elutasítva
expired = "expired" # Lejárt (biztonsági okokból)
class PendingAction(Base):
"""Négy szem elv: Műveletek, amik jóváhagyásra várnak."""
__tablename__ = "pending_actions"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True)
# Ki akarja csinálni?
requester_id = Column(Integer, ForeignKey("data.users.id"), nullable=False)
# Ki hagyta jóvá/utasította el?
approver_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
status = Column(Enum(ActionStatus), default=ActionStatus.pending, nullable=False)
# Milyen típusú művelet? (pl. "CHANGE_ROLE", "WALLET_ADJUST", "DELETE_LOGS")
action_type = Column(String(50), nullable=False)
# A művelet adatai JSON-ben (pl. {"user_id": 5, "new_role": "admin"})
payload = Column(JSON, nullable=False)
# Miért kell ez a művelet? (Indoklás kötelező az audit miatt)
reason = Column(String(255), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
expires_at = Column(DateTime(timezone=True), default=lambda: datetime.now() + timedelta(hours=24))
processed_at = Column(DateTime(timezone=True), nullable=True)
requester = relationship("User", foreign_keys=[requester_id])
approver = relationship("User", foreign_keys=[approver_id])

View File

@@ -1,5 +1,6 @@
from sqlalchemy import Column, Integer, String, Text, Boolean, UniqueConstraint
from app.db.base import Base
# JAVÍTÁS: Közvetlenül a base_class-ból importálunk, hogy elkerüljük a körkörös importot
from app.db.base_class import Base
class Translation(Base):
__tablename__ = "translations"
@@ -12,4 +13,4 @@ class Translation(Base):
key = Column(String(100), nullable=False, index=True)
lang_code = Column(String(5), nullable=False, index=True)
value = Column(Text, nullable=False)
is_published = Column(Boolean, default=False) # Publikálási állapot
is_published = Column(Boolean, default=False)