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:
Binary file not shown.
@@ -15,10 +15,6 @@ reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
async def get_current_token_payload(
|
||||
token: str = Depends(reusable_oauth2)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Kinyeri a token payload-ot DB hívás nélkül.
|
||||
Ez teszi lehetővé a gyors jogosultság-ellenőrzést.
|
||||
"""
|
||||
if token == "dev_bypass_active":
|
||||
return {
|
||||
"sub": "1",
|
||||
@@ -40,9 +36,6 @@ async def get_current_user(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
payload: Dict[str, Any] = Depends(get_current_token_payload),
|
||||
) -> User:
|
||||
"""
|
||||
Visszaadja a teljes User modellt. Akkor használjuk, ha módosítani kell az usert.
|
||||
"""
|
||||
user_id = payload.get("sub")
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token azonosítási hiba.")
|
||||
@@ -55,11 +48,18 @@ async def get_current_user(
|
||||
|
||||
return user
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> User:
|
||||
"""Ellenőrzi, hogy a felhasználó aktív-e."""
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="A felhasználói fiók zárolva van vagy inaktív."
|
||||
)
|
||||
return current_user
|
||||
|
||||
def check_min_rank(required_rank: int):
|
||||
"""
|
||||
Függőség-gyár: Ellenőrzi, hogy a felhasználó rangja eléri-e a minimumot.
|
||||
Használat: Depends(check_min_rank(60)) -> RegionAdmin+
|
||||
"""
|
||||
def rank_checker(payload: Dict[str, Any] = Depends(get_current_token_payload)):
|
||||
user_rank = payload.get("rank", 0)
|
||||
if user_rank < required_rank:
|
||||
|
||||
Binary file not shown.
@@ -1,22 +1,26 @@
|
||||
from fastapi import APIRouter
|
||||
from app.api.v1.endpoints import auth, catalog, assets, organizations, documents, services
|
||||
from app.api.v1.endpoints import auth, catalog, assets, organizations, documents, services, admin
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# Hitelesítés
|
||||
# Hitelesítés (Authentication)
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
||||
|
||||
# Szolgáltatások és Vadászat (Ez az új rész!)
|
||||
# Szolgáltatások és Vadászat (Service Hunt & Discovery)
|
||||
api_router.include_router(services.router, prefix="/services", tags=["Service Hunt & Discovery"])
|
||||
|
||||
# Katalógus
|
||||
# Katalógus (Vehicle Catalog)
|
||||
api_router.include_router(catalog.router, prefix="/catalog", tags=["Vehicle Catalog"])
|
||||
|
||||
# Eszközök (Járművek)
|
||||
# Eszközök / Járművek (Assets)
|
||||
api_router.include_router(assets.router, prefix="/assets", tags=["Assets"])
|
||||
|
||||
# Szervezetek
|
||||
# Szervezetek (Organizations)
|
||||
api_router.include_router(organizations.router, prefix="/organizations", tags=["Organizations"])
|
||||
|
||||
# Dokumentumok
|
||||
# Dokumentumok (Documents)
|
||||
api_router.include_router(documents.router, prefix="/documents", tags=["Documents"])
|
||||
|
||||
# --- 🛡️ SENTINEL ADMIN KONTROLL PANEL ---
|
||||
# Ez a rész tette láthatóvá az Admin API-t a felületen
|
||||
api_router.include_router(admin.router, prefix="/admin", tags=["Admin Control Center (Sentinel)"])
|
||||
BIN
backend/app/api/v1/endpoints/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backend/app/api/v1/endpoints/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
@@ -1,79 +1,115 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
from sqlalchemy import select, func
|
||||
from typing import List, Any, Dict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.api import deps
|
||||
from app.models.user import User, UserRole
|
||||
from app.models.system_settings import SystemSetting # ÚJ import
|
||||
from app.models.gamification import PointRule, LevelConfig, RegionalSetting
|
||||
from app.models.translation import Translation
|
||||
from app.models.identity import User, UserRole
|
||||
from app.models.system_config import SystemParameter
|
||||
from app.models.security import PendingAction, ActionStatus
|
||||
from app.models.history import AuditLog, LogSeverity
|
||||
from app.schemas.admin_security import PendingActionResponse, SecurityStatusResponse
|
||||
|
||||
from app.services.security_service import security_service
|
||||
# Feltételezve, hogy a JSON-alapú TranslationService-ed már készen van
|
||||
from app.services.translation_service import TranslationService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def check_admin_access(current_user: User, required_roles: List[UserRole]):
|
||||
if current_user.role not in required_roles:
|
||||
# --- 🛡️ ADMIN JOGOSULTSÁG ELLENŐRZŐ ---
|
||||
async def check_admin_access(current_user: User = Depends(deps.get_current_active_user)):
|
||||
if current_user.role not in [UserRole.admin, UserRole.superadmin]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Nincs jogosultságod ehhez a művelethez."
|
||||
detail="Admin jogosultság szükséges!"
|
||||
)
|
||||
return current_user
|
||||
|
||||
# --- ⚙️ ÚJ: DINAMIKUS RENDSZERBEÁLLÍTÁSOK (Pl. Jármű limit) ---
|
||||
# --- 1. SENTINEL: NÉGY SZEM ELV (Approval System) ---
|
||||
|
||||
@router.get("/settings", response_model=List[dict])
|
||||
async def get_all_system_settings(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
@router.get("/pending-actions", response_model=List[PendingActionResponse])
|
||||
async def list_pending_actions(
|
||||
db: AsyncSession = Depends(deps.get_db),
|
||||
admin: User = Depends(check_admin_access)
|
||||
):
|
||||
"""Az összes globális rendszerbeállítás listázása."""
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER])
|
||||
result = await db.execute(select(SystemSetting))
|
||||
settings = result.scalars().all()
|
||||
return [{"key": s.key, "value": s.value, "description": s.description} for s in settings]
|
||||
"""Jóváhagyásra váró kritikus kérések 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}")
|
||||
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 (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=400, detail=str(e))
|
||||
|
||||
# --- 2. SENTINEL: BIZTONSÁGI ÖSSZEGZÉS ---
|
||||
|
||||
@router.get("/security-status", response_model=SecurityStatusResponse)
|
||||
async def get_security_status(
|
||||
db: AsyncSession = Depends(deps.get_db),
|
||||
admin: User = Depends(check_admin_access)
|
||||
):
|
||||
"""Rendszerállapot: Zárolt júzerek és kritikus események."""
|
||||
day_ago = datetime.now() - timedelta(days=1)
|
||||
|
||||
crit_count = (await db.execute(select(func.count(AuditLog.id)).where(
|
||||
AuditLog.severity.in_([LogSeverity.critical, LogSeverity.emergency]),
|
||||
AuditLog.timestamp >= day_ago
|
||||
))).scalar() or 0
|
||||
|
||||
locked_count = (await db.execute(select(func.count(User.id)).where(
|
||||
User.is_active == False, User.is_deleted == False
|
||||
))).scalar() or 0
|
||||
|
||||
return {
|
||||
"total_pending": (await db.execute(select(func.count(PendingAction.id)).where(PendingAction.status == ActionStatus.pending))).scalar() or 0,
|
||||
"critical_logs_last_24h": crit_count,
|
||||
"emergency_locks_active": locked_count
|
||||
}
|
||||
|
||||
# --- 3. RENDSZERBEÁLLÍTÁSOK (Dynamic Config) ---
|
||||
|
||||
@router.get("/settings")
|
||||
async def get_settings(db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
|
||||
"""Minden globális paraméter (Gamification, Limitek stb.) lekérése."""
|
||||
result = await db.execute(select(SystemParameter))
|
||||
return result.scalars().all()
|
||||
|
||||
@router.put("/settings/{key}")
|
||||
async def update_system_setting(
|
||||
key: str,
|
||||
new_value: int, # Később lehet JSON is, ha komplexebb a beállítás
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
"""Egy adott beállítás (pl. FREE_VEHICLE_LIMIT) módosítása."""
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER])
|
||||
async def update_setting(key: str, value: Any, db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
|
||||
"""Paraméter módosítása és Audit Log generálása."""
|
||||
stmt = select(SystemParameter).where(SystemParameter.key == key)
|
||||
param = (await db.execute(stmt)).scalar_one_or_none()
|
||||
if not param:
|
||||
raise HTTPException(status_code=404, detail="Nincs ilyen beállítás.")
|
||||
|
||||
result = await db.execute(select(SystemSetting).where(SystemSetting.key == key))
|
||||
setting = result.scalar_one_or_none()
|
||||
old_val = param.value
|
||||
param.value = value
|
||||
|
||||
if not setting:
|
||||
raise HTTPException(status_code=404, detail="Beállítás nem található")
|
||||
|
||||
setting.value = new_value
|
||||
await security_service.log_event(
|
||||
db, admin.id, action="SETTING_CHANGE", severity=LogSeverity.warning,
|
||||
old_data={key: old_val}, new_data={key: value}
|
||||
)
|
||||
await db.commit()
|
||||
return {"status": "success", "key": key, "new_value": new_value}
|
||||
return {"status": "success", "key": key, "new_value": value}
|
||||
|
||||
# --- 🌍 JSON FORDÍTÁSOK KEZELÉSE ---
|
||||
|
||||
# --- 🌍 FORDÍTÁSOK KEZELÉSE (Meglévő kódod) ---
|
||||
|
||||
@router.post("/translations", status_code=status.HTTP_201_CREATED)
|
||||
async def add_translation_draft(
|
||||
key: str, lang: str, value: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
@router.post("/translations/sync")
|
||||
async def sync_translations_to_json(
|
||||
db: AsyncSession = Depends(deps.get_db),
|
||||
admin: User = Depends(check_admin_access)
|
||||
):
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
|
||||
new_t = Translation(key=key, lang_code=lang, value=value, is_published=False)
|
||||
db.add(new_t)
|
||||
await db.commit()
|
||||
return {"message": "Fordítás piszkozatként mentve. Ne felejtsd el publikálni!"}
|
||||
|
||||
@router.post("/translations/publish")
|
||||
async def publish_translations(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
|
||||
await TranslationService.publish_all(db)
|
||||
return {"message": "Sikeres publikálás! A változások minden szerveren élesedtek."}
|
||||
|
||||
"""Szinkronizálja az adatbázisban tárolt fordításokat a JSON fájlokba."""
|
||||
# A TranslationService-ben kell megírni a fájlbaíró logikát
|
||||
await TranslationService.export_to_json(db)
|
||||
return {"message": "JSON nyelvi fájlok frissítve."}
|
||||
Binary file not shown.
@@ -1,10 +1,16 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# --- Paths (ÚJ SZEKCIÓ) ---
|
||||
# Meghatározzuk a projekt gyökérmappáját és a statikus fájlok helyét
|
||||
BASE_DIR: Path = Path(__file__).resolve().parent.parent.parent
|
||||
STATIC_DIR: str = os.path.join(str(BASE_DIR), "static")
|
||||
|
||||
# --- General ---
|
||||
PROJECT_NAME: str = "Traffic Ecosystem SuperApp"
|
||||
VERSION: str = "1.0.0"
|
||||
@@ -16,8 +22,7 @@ class Settings(BaseSettings):
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 nap
|
||||
|
||||
# --- Initial Admin (ÚJ SZEKCIÓ) ---
|
||||
# Ezeket a .env-ből fogja venni
|
||||
# --- Initial Admin ---
|
||||
INITIAL_ADMIN_EMAIL: str = "admin@servicefinder.hu"
|
||||
INITIAL_ADMIN_PASSWORD: str = "Admin123!"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -15,6 +15,8 @@ from app.models.gamification import ( # noqa
|
||||
from app.models.system_config import SystemParameter # noqa
|
||||
from app.models.history import AuditLog, VehicleOwnership # noqa
|
||||
from app.models.document import Document # noqa
|
||||
from app.models.translation import Translation # noqa <--- HOZZÁADVA
|
||||
from app.models.core_logic import ( # noqa
|
||||
SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
|
||||
)
|
||||
from app.models.security import PendingAction # noqa <--- CSAK A BIZTONSÁG KEDVÉÉRT, HA EZ IS HIÁNYZOTT VOLNA
|
||||
@@ -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"
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/app/models/__pycache__/security.cpython-312.pyc
Normal file
BIN
backend/app/models/__pycache__/security.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/models/__pycache__/translation.cpython-312.pyc
Normal file
BIN
backend/app/models/__pycache__/translation.cpython-312.pyc
Normal file
Binary file not shown.
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
# EZ A JAVÍTÁS: A visszamutató kapcsolat definiálása
|
||||
# --- 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())
|
||||
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)
|
||||
|
||||
@@ -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())
|
||||
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")
|
||||
@@ -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"}
|
||||
|
||||
44
backend/app/models/security.py
Normal file
44
backend/app/models/security.py
Normal 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])
|
||||
@@ -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)
|
||||
BIN
backend/app/schemas/__pycache__/admin_security.cpython-312.pyc
Normal file
BIN
backend/app/schemas/__pycache__/admin_security.cpython-312.pyc
Normal file
Binary file not shown.
26
backend/app/schemas/admin_security.py
Normal file
26
backend/app/schemas/admin_security.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any, Dict, List
|
||||
from app.models.security import ActionStatus
|
||||
|
||||
class PendingActionResponse(BaseModel):
|
||||
id: int
|
||||
requester_id: int
|
||||
action_type: str
|
||||
payload: Dict[str, Any]
|
||||
reason: str
|
||||
status: ActionStatus
|
||||
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
|
||||
|
||||
class SecurityStatusResponse(BaseModel):
|
||||
total_pending: int
|
||||
critical_logs_last_24h: int
|
||||
emergency_locks_active: int
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import logging
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
@@ -18,18 +19,15 @@ from app.services.email_manager import email_manager
|
||||
from app.core.config import settings
|
||||
from app.services.config_service import config
|
||||
from app.services.geo_service import GeoService
|
||||
from app.services.security_service import security_service # Sentinel integráció
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AuthService:
|
||||
@staticmethod
|
||||
async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
|
||||
"""
|
||||
Step 1: Lite Regisztráció (Master Book 1.1)
|
||||
Új User és ideiglenes Person rekord létrehozása nyelvi és időzóna adatokkal.
|
||||
"""
|
||||
"""Step 1: Lite Regisztráció."""
|
||||
try:
|
||||
# Ideiglenes Person rekord a KYC-ig
|
||||
new_person = Person(
|
||||
first_name=user_in.first_name,
|
||||
last_name=user_in.last_name,
|
||||
@@ -46,14 +44,12 @@ class AuthService:
|
||||
is_active=False,
|
||||
is_deleted=False,
|
||||
region_code=user_in.region_code,
|
||||
# --- NYELVI ÉS ADMIN BEÁLLÍTÁSOK MENTÉSE ---
|
||||
preferred_language=user_in.lang,
|
||||
timezone=user_in.timezone
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
|
||||
# Regisztrációs token generálása
|
||||
reg_hours = await config.get_setting("auth_registration_hours", region_code=user_in.region_code, default=48)
|
||||
token_val = uuid.uuid4()
|
||||
db.add(VerificationToken(
|
||||
@@ -63,14 +59,12 @@ class AuthService:
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reg_hours))
|
||||
))
|
||||
|
||||
# --- EMAIL KÜLDÉSE A VÁLASZTOTT NYELVEN ---
|
||||
# Master Book 3.2: Nincs manuális subject, a nyelvi kulcs alapján töltődik be
|
||||
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
|
||||
await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="reg", # hu.json: email.reg_subject, reg_greeting stb.
|
||||
template_key="reg",
|
||||
variables={"first_name": user_in.first_name, "link": verification_link},
|
||||
lang=user_in.lang # Dinamikus nyelvválasztás
|
||||
lang=user_in.lang
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
@@ -83,23 +77,16 @@ class AuthService:
|
||||
|
||||
@staticmethod
|
||||
async def complete_kyc(db: AsyncSession, user_id: int, kyc_in: UserKYCComplete):
|
||||
"""
|
||||
1.3. Fázis: Atomi Tranzakció & Shadow Identity
|
||||
Felismeri a visszatérő Person-t, de új User-ként, izolált flottával indít.
|
||||
Frissíti a nyelvi és pénzügyi beállításokat.
|
||||
"""
|
||||
"""1.3. Fázis: Atomi Tranzakció & Shadow Identity."""
|
||||
try:
|
||||
# 1. Aktuális technikai User lekérése
|
||||
stmt = select(User).options(joinedload(User.person)).where(User.id == user_id)
|
||||
res = await db.execute(stmt)
|
||||
user = res.scalar_one_or_none()
|
||||
if not user: return None
|
||||
|
||||
# --- PÉNZNEM PREFERENCIA FRISSÍTÉSE ---
|
||||
if hasattr(kyc_in, 'preferred_currency') and kyc_in.preferred_currency:
|
||||
user.preferred_currency = kyc_in.preferred_currency
|
||||
|
||||
# 2. Shadow Identity Ellenőrzése
|
||||
identity_stmt = select(Person).where(and_(
|
||||
Person.mothers_last_name == kyc_in.mothers_last_name,
|
||||
Person.mothers_first_name == kyc_in.mothers_first_name,
|
||||
@@ -115,7 +102,6 @@ class AuthService:
|
||||
else:
|
||||
active_person = user.person
|
||||
|
||||
# 3. Címkezelés
|
||||
addr_id = await GeoService.get_or_create_full_address(
|
||||
db,
|
||||
zip_code=kyc_in.address_zip,
|
||||
@@ -126,7 +112,6 @@ class AuthService:
|
||||
parcel_id=kyc_in.address_hrsz
|
||||
)
|
||||
|
||||
# 4. Person adatok frissítése
|
||||
active_person.mothers_last_name = kyc_in.mothers_last_name
|
||||
active_person.mothers_first_name = kyc_in.mothers_first_name
|
||||
active_person.birth_place = kyc_in.birth_place
|
||||
@@ -137,7 +122,6 @@ class AuthService:
|
||||
active_person.ice_contact = jsonable_encoder(kyc_in.ice_contact)
|
||||
active_person.is_active = True
|
||||
|
||||
# 5. Új, izolált INDIVIDUAL szervezet (4.2.3) i18n beállításokkal
|
||||
new_org = Organization(
|
||||
full_name=f"{active_person.last_name} {active_person.first_name} Egyéni Flotta",
|
||||
name=f"{active_person.last_name} Flotta",
|
||||
@@ -146,7 +130,6 @@ class AuthService:
|
||||
is_transferable=False,
|
||||
is_active=True,
|
||||
status="verified",
|
||||
# Megörökölt adminisztrációs adatok
|
||||
language=user.preferred_language,
|
||||
default_currency=user.preferred_currency,
|
||||
country_code=user.region_code
|
||||
@@ -154,7 +137,6 @@ class AuthService:
|
||||
db.add(new_org)
|
||||
await db.flush()
|
||||
|
||||
# 6. Tagság és Jogosultságok
|
||||
db.add(OrganizationMember(
|
||||
organization_id=new_org.id,
|
||||
user_id=user.id,
|
||||
@@ -162,7 +144,6 @@ class AuthService:
|
||||
permissions={"can_add_asset": True, "can_view_costs": True, "is_admin": True}
|
||||
))
|
||||
|
||||
# 7. Wallet & Stats
|
||||
db.add(Wallet(
|
||||
user_id=user.id,
|
||||
coin_balance=0,
|
||||
@@ -171,7 +152,6 @@ class AuthService:
|
||||
))
|
||||
db.add(UserStats(user_id=user.id, total_xp=0, current_level=1))
|
||||
|
||||
# 8. Aktiválás
|
||||
user.is_active = True
|
||||
|
||||
await db.commit()
|
||||
@@ -182,6 +162,39 @@ class AuthService:
|
||||
logger.error(f"KYC Atomi Tranzakció Hiba: {str(e)}")
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
async def soft_delete_user(db: AsyncSession, user_id: int, reason: str, actor_id: int):
|
||||
"""
|
||||
Step 2 utáni Soft-Delete: Email felszabadítás és izoláció.
|
||||
Az email átnevezésre kerül, így az eredeti cím újra regisztrálható 'tiszta lappal'.
|
||||
"""
|
||||
stmt = select(User).where(User.id == user_id)
|
||||
user = (await db.execute(stmt)).scalar_one_or_none()
|
||||
|
||||
if not user or user.is_deleted:
|
||||
return False
|
||||
|
||||
old_email = user.email
|
||||
# Email felszabadítása: deleted_ID_TIMESTAMP_EMAIL formátumban
|
||||
user.email = f"deleted_{user.id}_{datetime.now().strftime('%Y%m%d')}_{old_email}"
|
||||
user.is_deleted = True
|
||||
user.is_active = False
|
||||
|
||||
# Sentinel AuditLog bejegyzés
|
||||
await security_service.log_event(
|
||||
db,
|
||||
user_id=actor_id,
|
||||
action="USER_SOFT_DELETE",
|
||||
severity="warning",
|
||||
target_type="User",
|
||||
target_id=str(user_id),
|
||||
old_data={"email": old_email},
|
||||
new_data={"is_deleted": True, "reason": reason}
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def verify_email(db: AsyncSession, token_str: str):
|
||||
try:
|
||||
@@ -227,13 +240,12 @@ class AuthService:
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reset_hours))
|
||||
))
|
||||
|
||||
# --- EMAIL KÜLDÉSE A FELHASZNÁLÓ SAJÁT NYELVÉN ---
|
||||
reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}"
|
||||
await email_manager.send_email(
|
||||
recipient=email,
|
||||
template_key="pwd_reset", # hu.json: email.pwd_reset_subject stb.
|
||||
template_key="pwd_reset",
|
||||
variables={"link": reset_link},
|
||||
lang=user.preferred_language # Adatbázisból kinyert nyelv
|
||||
lang=user.preferred_language
|
||||
)
|
||||
await db.commit()
|
||||
return "success"
|
||||
|
||||
@@ -1,47 +1,106 @@
|
||||
import logging
|
||||
import math
|
||||
from decimal import Decimal
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.gamification import UserStats, PointsLedger
|
||||
import math
|
||||
from app.models.identity import User, Wallet
|
||||
from app.models.core_logic import CreditTransaction
|
||||
from app.models.system_config import SystemParameter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GamificationService:
|
||||
@staticmethod
|
||||
async def process_activity(db: AsyncSession, user_id: int, xp_amount: int, social_amount: int, reason: str):
|
||||
"""
|
||||
XP növelés, Szintlépés csekk és Automata Kredit váltás.
|
||||
"""
|
||||
# 1. User statisztika lekérése
|
||||
stmt = select(UserStats).where(UserStats.user_id == user_id)
|
||||
stats = (await db.execute(stmt)).scalar_one_or_none()
|
||||
async def get_config(db: AsyncSession):
|
||||
"""Kiolvassa a GAMIFICATION_MASTER_CONFIG-ot a rendszerparaméterekből."""
|
||||
stmt = select(SystemParameter).where(SystemParameter.key == "GAMIFICATION_MASTER_CONFIG")
|
||||
res = await db.execute(stmt)
|
||||
param = res.scalar_one_or_none()
|
||||
return param.value if param else {
|
||||
"xp_logic": {"base_xp": 500, "exponent": 1.5},
|
||||
"penalty_logic": {
|
||||
"thresholds": {"level_1": 100, "level_2": 500, "level_3": 1000},
|
||||
"multipliers": {"level_0": 1.0, "level_1": 0.5, "level_2": 0.1, "level_3": 0.0},
|
||||
"recovery_rate": 0.5
|
||||
},
|
||||
"conversion_logic": {"social_to_credit_rate": 100},
|
||||
"level_rewards": {"credits_per_10_levels": 50},
|
||||
"blocked_roles": ["superadmin", "service_bot"]
|
||||
}
|
||||
|
||||
async def process_activity(self, db: AsyncSession, user_id: int, xp_amount: int, social_amount: int, reason: str, is_penalty: bool = False):
|
||||
"""A 'Bíró' logika: Ellenőriz, büntet, jutalmaz és szintez."""
|
||||
config = await self.get_config(db)
|
||||
|
||||
# 1. Jogosultság ellenőrzése
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user = (await db.execute(user_stmt)).scalar_one_or_none()
|
||||
if not user or user.is_deleted or user.role.value in config.get("blocked_roles", []):
|
||||
return None
|
||||
|
||||
# 2. Stats lekérése
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == user_id)
|
||||
stats = (await db.execute(stats_stmt)).scalar_one_or_none()
|
||||
if not stats:
|
||||
stats = UserStats(user_id=user_id, total_xp=0, social_points=0, current_level=1, credits=0)
|
||||
stats = UserStats(user_id=user_id)
|
||||
db.add(stats)
|
||||
|
||||
# 2. Részletes Logolás (PointsLedger) - A visszakövethetőség miatt
|
||||
db.add(PointsLedger(
|
||||
user_id=user_id,
|
||||
xp_gain=xp_amount,
|
||||
social_gain=social_amount,
|
||||
reason=reason
|
||||
))
|
||||
# 3. Büntető logika (Penalty)
|
||||
if is_penalty:
|
||||
stats.penalty_points += xp_amount
|
||||
th = config["penalty_logic"]["thresholds"]
|
||||
if stats.penalty_points >= th["level_3"]: stats.restriction_level = 3
|
||||
elif stats.penalty_points >= th["level_2"]: stats.restriction_level = 2
|
||||
elif stats.penalty_points >= th["level_1"]: stats.restriction_level = 1
|
||||
|
||||
# 3. XP és Szintlépés (Nehezedő görbe)
|
||||
stats.total_xp += xp_amount
|
||||
# Képlet: Level = (XP / 500)^(1/1.5)
|
||||
new_level = int((stats.total_xp / 500) ** (1/1.5)) + 1
|
||||
if new_level > stats.current_level:
|
||||
stats.current_level = new_level
|
||||
db.add(PointsLedger(user_id=user_id, points=0, penalty_change=xp_amount, reason=f"PENALTY: {reason}"))
|
||||
await db.commit()
|
||||
return stats
|
||||
|
||||
# 4. Automata Kredit váltás
|
||||
# Példa: Minden 100 Social pont automatikusan 1 Kredit lesz
|
||||
stats.social_points += social_amount
|
||||
if stats.social_points >= 100:
|
||||
new_credits = stats.social_points // 100
|
||||
stats.credits += new_credits
|
||||
stats.social_points %= 100 # A maradék megmarad a következő váltáshoz
|
||||
# 4. Dinamikus szorzó alkalmazása
|
||||
multipliers = config["penalty_logic"]["multipliers"]
|
||||
multiplier = multipliers.get(f"level_{stats.restriction_level}", 1.0)
|
||||
|
||||
# Külön log a váltásról
|
||||
db.add(PointsLedger(user_id=user_id, reason=f"Auto-conversion: {new_credits} Credits", credits_change=new_credits))
|
||||
if multiplier <= 0:
|
||||
logger.warning(f"User {user_id} activity blocked (Level {stats.restriction_level})")
|
||||
return stats
|
||||
|
||||
# 5. XP, Ledolgozás és Szintlépés
|
||||
final_xp = int(xp_amount * multiplier)
|
||||
if final_xp > 0:
|
||||
stats.total_xp += final_xp
|
||||
if stats.penalty_points > 0:
|
||||
rec_rate = config["penalty_logic"]["recovery_rate"]
|
||||
stats.penalty_points = max(0, stats.penalty_points - int(final_xp * rec_rate))
|
||||
|
||||
xp_cfg = config["xp_logic"]
|
||||
new_level = int((stats.total_xp / xp_cfg["base_xp"]) ** (1/xp_cfg["exponent"])) + 1
|
||||
if new_level > stats.current_level:
|
||||
if new_level % 10 == 0:
|
||||
reward = config["level_rewards"]["credits_per_10_levels"]
|
||||
await self._add_credits(db, user_id, reward, f"Level {new_level} Achievement Bonus")
|
||||
stats.current_level = new_level
|
||||
|
||||
# 6. Social pont és váltás
|
||||
final_social = int(social_amount * multiplier)
|
||||
if final_social > 0:
|
||||
stats.social_points += final_social
|
||||
rate = config["conversion_logic"]["social_to_credit_rate"]
|
||||
if stats.social_points >= rate:
|
||||
new_credits = stats.social_points // rate
|
||||
stats.social_points %= rate
|
||||
await self._add_credits(db, user_id, new_credits, "Social conversion")
|
||||
|
||||
db.add(PointsLedger(user_id=user_id, points=final_xp, reason=reason))
|
||||
await db.commit()
|
||||
return stats
|
||||
|
||||
async def _add_credits(self, db: AsyncSession, user_id: int, amount: int, reason: str):
|
||||
wallet_stmt = select(Wallet).where(Wallet.user_id == user_id)
|
||||
wallet = (await db.execute(wallet_stmt)).scalar_one_or_none()
|
||||
if wallet:
|
||||
wallet.credit_balance += Decimal(amount)
|
||||
db.add(CreditTransaction(org_id=None, amount=Decimal(amount), description=reason))
|
||||
|
||||
gamification_service = GamificationService()
|
||||
169
backend/app/services/security_service.py
Normal file
169
backend/app/services/security_service.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Any, Dict
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_
|
||||
from app.models.security import PendingAction, ActionStatus
|
||||
from app.models.history import AuditLog, LogSeverity
|
||||
from app.models.identity import User
|
||||
from app.models.system_config import SystemParameter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SecurityService:
|
||||
|
||||
@staticmethod
|
||||
async def get_sec_config(db: AsyncSession) -> Dict[str, Any]:
|
||||
"""Lekéri a biztonsági korlátokat a központi rendszerparaméterekből."""
|
||||
keys = ["SECURITY_MAX_RECORDS_PER_HOUR", "SECURITY_DUAL_CONTROL_ENABLED"]
|
||||
stmt = select(SystemParameter).where(SystemParameter.key.in_(keys))
|
||||
res = await db.execute(stmt)
|
||||
params = {p.key: p.value for p in res.scalars().all()}
|
||||
|
||||
return {
|
||||
"max_records": int(params.get("SECURITY_MAX_RECORDS_PER_HOUR", 500)),
|
||||
"dual_control": str(params.get("SECURITY_DUAL_CONTROL_ENABLED", "true")).lower() == "true"
|
||||
}
|
||||
|
||||
# --- 1. SZINT: AUDIT & LOGGING (A Mindenlátó Szem) ---
|
||||
async def log_event(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
user_id: Optional[int],
|
||||
action: str,
|
||||
severity: LogSeverity,
|
||||
old_data: Optional[Dict] = None,
|
||||
new_data: Optional[Dict] = None,
|
||||
ip: Optional[str] = None,
|
||||
ua: Optional[str] = None,
|
||||
target_type: Optional[str] = None,
|
||||
target_id: Optional[str] = None,
|
||||
reason: Optional[str] = None
|
||||
):
|
||||
"""Minden rendszerművelet rögzítése és azonnali biztonsági elemzése."""
|
||||
new_log = AuditLog(
|
||||
user_id=user_id,
|
||||
severity=severity,
|
||||
action=action,
|
||||
target_type=target_type,
|
||||
target_id=target_id,
|
||||
old_data=old_data,
|
||||
new_data=new_data,
|
||||
ip_address=ip,
|
||||
user_agent=ua
|
||||
)
|
||||
db.add(new_log)
|
||||
|
||||
# Ha a szint EMERGENCY, azonnal lőjük le a júzert
|
||||
if severity == LogSeverity.emergency:
|
||||
await self._execute_emergency_lock(db, user_id, f"Auto-lock triggered by: {action}")
|
||||
|
||||
await db.commit()
|
||||
|
||||
# --- 2. SZINT: PENDING ACTIONS (Négy szem elv) ---
|
||||
async def request_action(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
requester_id: int,
|
||||
action_type: str,
|
||||
payload: Dict,
|
||||
reason: str
|
||||
):
|
||||
"""Kritikus művelet kezdeményezése jóváhagyásra (nem hajtódik végre azonnal)."""
|
||||
new_action = PendingAction(
|
||||
requester_id=requester_id,
|
||||
action_type=action_type,
|
||||
payload=payload,
|
||||
reason=reason,
|
||||
status=ActionStatus.pending
|
||||
)
|
||||
db.add(new_action)
|
||||
|
||||
await self.log_event(
|
||||
db, requester_id,
|
||||
action=f"REQUEST_{action_type}",
|
||||
severity=LogSeverity.critical,
|
||||
new_data=payload,
|
||||
reason=f"Approval requested: {reason}"
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
return new_action
|
||||
|
||||
async def approve_action(self, db: AsyncSession, approver_id: int, action_id: int):
|
||||
"""Művelet végrehajtása egy második admin által."""
|
||||
stmt = select(PendingAction).where(PendingAction.id == action_id)
|
||||
action = (await db.execute(stmt)).scalar_one_or_none()
|
||||
|
||||
if not action or action.status != ActionStatus.pending:
|
||||
raise Exception("A művelet nem található vagy már feldolgozták.")
|
||||
|
||||
if action.requester_id == approver_id:
|
||||
raise Exception("Önmagad kérését nem hagyhatod jóvá! (Négy szem elv)")
|
||||
|
||||
# ITT TÖRTÉNIK A TÉNYLEGES ÜZLETI LOGIKA (Példa: Rangmódosítás)
|
||||
if action.action_type == "CHANGE_ROLE":
|
||||
user_id = action.payload.get("user_id")
|
||||
new_role = action.payload.get("new_role")
|
||||
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user = (await db.execute(user_stmt)).scalar_one_or_none()
|
||||
if user:
|
||||
user.role = new_role
|
||||
logger.info(f"Role for user {user_id} changed to {new_role} via approved action {action_id}")
|
||||
|
||||
action.status = ActionStatus.approved
|
||||
action.approver_id = approver_id
|
||||
action.processed_at = func.now()
|
||||
|
||||
await self.log_event(
|
||||
db, approver_id,
|
||||
action=f"APPROVE_{action.action_type}",
|
||||
severity=LogSeverity.info,
|
||||
target_id=str(action.id),
|
||||
reason=f"Approved action requested by {action.requester_id}"
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
# --- 3. SZINT: DATA THROTTLING & EMERGENCY LOCK ---
|
||||
async def check_data_access_limit(self, db: AsyncSession, user_id: int):
|
||||
"""Figyeli a tömeges adatlekérést (Adatlopás elleni védelem)."""
|
||||
config = await self.get_sec_config(db)
|
||||
one_hour_ago = datetime.now() - timedelta(hours=1)
|
||||
|
||||
# Megszámoljuk az utolsó egy óra GET (lekérési) logjait
|
||||
stmt = select(func.count(AuditLog.id)).where(
|
||||
and_(
|
||||
AuditLog.user_id == user_id,
|
||||
AuditLog.timestamp >= one_hour_ago,
|
||||
AuditLog.action.like("GET_%")
|
||||
)
|
||||
)
|
||||
count = (await db.execute(stmt)).scalar() or 0
|
||||
|
||||
if count > config["max_records"]:
|
||||
await self.log_event(
|
||||
db, user_id,
|
||||
action="MASS_DATA_ACCESS_DETECTED",
|
||||
severity=LogSeverity.emergency,
|
||||
reason=f"Access count: {count} (Limit: {config['max_records']})"
|
||||
)
|
||||
# A log_event automatikusan hívja a _execute_emergency_lock-ot
|
||||
return False
|
||||
return True
|
||||
|
||||
async def _execute_emergency_lock(self, db: AsyncSession, user_id: int, reason: str):
|
||||
"""Azonnali fiókfelfüggesztés vészhelyzet esetén."""
|
||||
if not user_id: return
|
||||
|
||||
stmt = select(User).where(User.id == user_id)
|
||||
user = (await db.execute(stmt)).scalar_one_or_none()
|
||||
|
||||
if user:
|
||||
user.is_active = False
|
||||
logger.critical(f"🚨 SECURITY EMERGENCY LOCK: User {user_id} suspended. Reason: {reason}")
|
||||
# Itt lehetne bekötni egy külső SMS/Slack/Email riasztást
|
||||
|
||||
security_service = SecurityService()
|
||||
@@ -1,15 +1,21 @@
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update
|
||||
from app.models.translation import Translation
|
||||
from typing import Dict
|
||||
from app.core.config import settings
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TranslationService:
|
||||
# Ez a memória-cache tárolja az élesített szövegeket
|
||||
# Memória-cache a szerveroldali hibaüzenetekhez és emailekhez
|
||||
_published_cache: Dict[str, Dict[str, str]] = {}
|
||||
|
||||
@classmethod
|
||||
async def load_cache(cls, db: AsyncSession):
|
||||
"""Betölti az összes PUBLIKÁLT fordítást az adatbázisból a memóriába."""
|
||||
"""Betölti a publikált szövegeket a memóriába az adatbázisból."""
|
||||
result = await db.execute(
|
||||
select(Translation).where(Translation.is_published == True)
|
||||
)
|
||||
@@ -20,27 +26,80 @@ class TranslationService:
|
||||
if t.lang_code not in cls._published_cache:
|
||||
cls._published_cache[t.lang_code] = {}
|
||||
cls._published_cache[t.lang_code][t.key] = t.value
|
||||
print(f"🌍 i18n Cache: {len(translations)} szöveg élesítve.")
|
||||
logger.info(f"🌍 i18n Cache: {len(translations)} szöveg betöltve.")
|
||||
|
||||
@classmethod
|
||||
def get_text(cls, key: str, lang: str = "en") -> str:
|
||||
"""Villámgyors lekérés a memóriából Fallback logikával."""
|
||||
# 1. Kért nyelv
|
||||
def get_text(cls, key: str, lang: str = "hu", variables: Optional[Dict[str, Any]] = None) -> str:
|
||||
"""
|
||||
Szerveroldali lekérés Fallback (EN) logikával és változó behelyettesítéssel.
|
||||
Példa: get_text("AUTH.WELCOME", "hu", {"name": "Péter"})
|
||||
"""
|
||||
# 1. Kért nyelv lekérése
|
||||
text = cls._published_cache.get(lang, {}).get(key)
|
||||
if text: return text
|
||||
|
||||
# 2. Fallback: Angol
|
||||
if lang != "en":
|
||||
# 2. Fallback angolra, ha nincs meg a kért nyelven
|
||||
if not text and lang != "en":
|
||||
text = cls._published_cache.get("en", {}).get(key)
|
||||
if text: return text
|
||||
|
||||
return f"[{key}]"
|
||||
# 3. Ha sehol nincs meg, adjuk vissza a kulcsot
|
||||
if not text:
|
||||
return f"[{key}]"
|
||||
|
||||
# 4. Változók behelyettesítése (pl. {{name}})
|
||||
if variables:
|
||||
for k, v in variables.items():
|
||||
text = text.replace(f"{{{{{k}}}}}", str(v))
|
||||
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
async def publish_all(cls, db: AsyncSession):
|
||||
"""Élesíti a piszkozatokat és frissíti a szerver memóriáját."""
|
||||
"""Minden piszkozatot élesít, frissíti a memóriát és legenerálja a JSON-öket."""
|
||||
await db.execute(
|
||||
update(Translation).where(Translation.is_published == False).values(is_published=True)
|
||||
)
|
||||
await db.commit()
|
||||
await cls.load_cache(db)
|
||||
await cls.export_to_json(db)
|
||||
|
||||
@staticmethod
|
||||
async def export_to_json(db: AsyncSession):
|
||||
"""
|
||||
Adatbázis -> Hierarchikus JSON export.
|
||||
'AUTH.LOGIN.TITLE' -> { "AUTH": { "LOGIN": { "TITLE": "..." } } }
|
||||
"""
|
||||
stmt = select(Translation).where(Translation.is_published == True)
|
||||
result = await db.execute(stmt)
|
||||
translations = result.scalars().all()
|
||||
|
||||
languages: Dict[str, Any] = {}
|
||||
for t in translations:
|
||||
if t.lang_code not in languages:
|
||||
languages[t.lang_code] = {}
|
||||
|
||||
# Hierarchikus struktúra felépítése
|
||||
parts = t.key.split('.')
|
||||
current_level = languages[t.lang_code]
|
||||
for part in parts[:-1]:
|
||||
if part not in current_level:
|
||||
current_level[part] = {}
|
||||
current_level = current_level[part]
|
||||
|
||||
current_level[parts[-1]] = t.value
|
||||
|
||||
# Fájlok mentése
|
||||
locales_path = os.path.join(settings.STATIC_DIR, "locales")
|
||||
os.makedirs(locales_path, exist_ok=True)
|
||||
|
||||
for lang, content in languages.items():
|
||||
file_path = os.path.join(locales_path, f"{lang}.json")
|
||||
try:
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(content, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"🚀 JSON legenerálva: {file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Fájl hiba ({lang}): {str(e)}")
|
||||
|
||||
return True
|
||||
|
||||
translation_service = TranslationService()
|
||||
@@ -0,0 +1,210 @@
|
||||
"""create_translation_and_security_tables
|
||||
|
||||
Revision ID: 134d92edd430
|
||||
Revises: bc5669f12ffd
|
||||
Create Date: 2026-02-10 20:04:23.924164
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '134d92edd430'
|
||||
down_revision: Union[str, Sequence[str], None] = 'bc5669f12ffd'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('translations',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=100), nullable=False),
|
||||
sa.Column('lang_code', sa.String(length=5), nullable=False),
|
||||
sa.Column('value', sa.Text(), nullable=False),
|
||||
sa.Column('is_published', sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('key', 'lang_code', name='uq_translation_key_lang'),
|
||||
schema='data'
|
||||
)
|
||||
op.create_index(op.f('ix_data_translations_id'), 'translations', ['id'], unique=False, schema='data')
|
||||
op.create_index(op.f('ix_data_translations_key'), 'translations', ['key'], unique=False, schema='data')
|
||||
op.create_index(op.f('ix_data_translations_lang_code'), 'translations', ['lang_code'], unique=False, schema='data')
|
||||
op.create_table('pending_actions',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('requester_id', sa.Integer(), nullable=False),
|
||||
sa.Column('approver_id', sa.Integer(), nullable=True),
|
||||
sa.Column('status', sa.Enum('pending', 'approved', 'rejected', 'expired', name='actionstatus'), nullable=False),
|
||||
sa.Column('action_type', sa.String(length=50), nullable=False),
|
||||
sa.Column('payload', sa.JSON(), nullable=False),
|
||||
sa.Column('reason', sa.String(length=255), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('processed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(['approver_id'], ['data.users.id'], ),
|
||||
sa.ForeignKeyConstraint(['requester_id'], ['data.users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
schema='data'
|
||||
)
|
||||
op.create_index(op.f('ix_data_pending_actions_id'), 'pending_actions', ['id'], unique=False, schema='data')
|
||||
op.drop_constraint(op.f('addresses_postal_code_id_fkey'), 'addresses', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_costs_organization_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_driver_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_asset_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_costs', 'users', ['driver_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_events_asset_id_fkey'), 'asset_events', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_events', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_financials_asset_id_fkey'), 'asset_financials', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_financials', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_telemetry', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('assets_catalog_id_fkey'), 'assets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'assets', 'vehicle_catalog', ['catalog_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('audit_logs_user_id_fkey'), 'audit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'audit_logs', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('credit_logs_org_id_fkey'), 'credit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'credit_logs', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('documents_uploaded_by_fkey'), 'documents', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'documents', 'users', ['uploaded_by'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.drop_constraint(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('organization_members_user_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organization_members_organization_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organization_members', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organization_members', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(op.f('organizations_owner_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organizations_address_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organizations', 'users', ['owner_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organizations', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('persons_address_id_fkey'), 'persons', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'persons', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('points_ledger_user_id_fkey'), 'points_ledger', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'points_ledger', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('ratings_author_id_fkey'), 'ratings', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'ratings', 'users', ['author_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('service_specialties_parent_id_fkey'), 'service_specialties', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'service_specialties', 'service_specialties', ['parent_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_badges_user_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.drop_constraint(op.f('user_badges_badge_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_badges', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'user_badges', 'badges', ['badge_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_stats_user_id_fkey'), 'user_stats', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_stats', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('users_person_id_fkey'), 'users', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'users', 'persons', ['person_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'verification_tokens', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data', ondelete='CASCADE')
|
||||
op.drop_constraint(op.f('wallets_user_id_fkey'), 'wallets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'wallets', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'wallets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('wallets_user_id_fkey'), 'wallets', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'verification_tokens', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', 'users', ['user_id'], ['id'], ondelete='CASCADE')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'])
|
||||
op.drop_constraint(None, 'users', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('users_person_id_fkey'), 'users', 'persons', ['person_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_stats', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_stats_user_id_fkey'), 'user_stats', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_badges_badge_id_fkey'), 'user_badges', 'badges', ['badge_id'], ['id'])
|
||||
op.create_foreign_key(op.f('user_badges_user_id_fkey'), 'user_badges', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'service_specialties', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('service_specialties_parent_id_fkey'), 'service_specialties', 'service_specialties', ['parent_id'], ['id'])
|
||||
op.drop_constraint(None, 'ratings', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('ratings_author_id_fkey'), 'ratings', 'users', ['author_id'], ['id'])
|
||||
op.drop_constraint(None, 'points_ledger', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('points_ledger_user_id_fkey'), 'points_ledger', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'persons', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('persons_address_id_fkey'), 'persons', 'addresses', ['address_id'], ['id'])
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organizations_address_id_fkey'), 'organizations', 'addresses', ['address_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organizations_owner_id_fkey'), 'organizations', 'users', ['owner_id'], ['id'])
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organization_members_organization_id_fkey'), 'organization_members', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organization_members_user_id_fkey'), 'organization_members', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'])
|
||||
op.create_foreign_key(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'geo_streets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
op.drop_constraint(None, 'documents', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('documents_uploaded_by_fkey'), 'documents', 'users', ['uploaded_by'], ['id'])
|
||||
op.drop_constraint(None, 'credit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('credit_logs_org_id_fkey'), 'credit_logs', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'audit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('audit_logs_user_id_fkey'), 'audit_logs', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'assets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('assets_catalog_id_fkey'), 'assets', 'vehicle_catalog', ['catalog_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_telemetry', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_financials', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_financials_asset_id_fkey'), 'asset_financials', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_events', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_events_asset_id_fkey'), 'asset_events', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_costs_asset_id_fkey'), 'asset_costs', 'assets', ['asset_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_driver_id_fkey'), 'asset_costs', 'users', ['driver_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_organization_id_fkey'), 'asset_costs', 'organizations', ['organization_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', 'assets', ['asset_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', 'organizations', ['organization_id'], ['id'])
|
||||
op.drop_constraint(None, 'addresses', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('addresses_postal_code_id_fkey'), 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
op.drop_index(op.f('ix_data_pending_actions_id'), table_name='pending_actions', schema='data')
|
||||
op.drop_table('pending_actions', schema='data')
|
||||
op.drop_index(op.f('ix_data_translations_lang_code'), table_name='translations', schema='data')
|
||||
op.drop_index(op.f('ix_data_translations_key'), table_name='translations', schema='data')
|
||||
op.drop_index(op.f('ix_data_translations_id'), table_name='translations', schema='data')
|
||||
op.drop_table('translations', schema='data')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,186 @@
|
||||
"""add_lang_and_region_to_user
|
||||
|
||||
Revision ID: 6197bfddfb4f
|
||||
Revises: 134d92edd430
|
||||
Create Date: 2026-02-10 20:46:57.170479
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '6197bfddfb4f'
|
||||
down_revision: Union[str, Sequence[str], None] = '134d92edd430'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(op.f('addresses_postal_code_id_fkey'), 'addresses', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_costs_asset_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_organization_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_driver_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_costs', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'users', ['driver_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_events_asset_id_fkey'), 'asset_events', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_events', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_financials_asset_id_fkey'), 'asset_financials', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_financials', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_telemetry', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('assets_catalog_id_fkey'), 'assets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'assets', 'vehicle_catalog', ['catalog_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('audit_logs_user_id_fkey'), 'audit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'audit_logs', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('credit_logs_org_id_fkey'), 'credit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'credit_logs', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('documents_uploaded_by_fkey'), 'documents', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'documents', 'users', ['uploaded_by'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.drop_constraint(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('organization_members_user_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organization_members_organization_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organization_members', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organization_members', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(op.f('organizations_address_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organizations_owner_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organizations', 'users', ['owner_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organizations', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('pending_actions_approver_id_fkey'), 'pending_actions', type_='foreignkey')
|
||||
op.drop_constraint(op.f('pending_actions_requester_id_fkey'), 'pending_actions', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'pending_actions', 'users', ['requester_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'pending_actions', 'users', ['approver_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('persons_address_id_fkey'), 'persons', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'persons', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('points_ledger_user_id_fkey'), 'points_ledger', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'points_ledger', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('ratings_author_id_fkey'), 'ratings', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'ratings', 'users', ['author_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('service_specialties_parent_id_fkey'), 'service_specialties', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'service_specialties', 'service_specialties', ['parent_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_badges_badge_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.drop_constraint(op.f('user_badges_user_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_badges', 'badges', ['badge_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'user_badges', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_stats_user_id_fkey'), 'user_stats', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_stats', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.add_column('users', sa.Column('preferred_language', sa.String(length=5), server_default='hu', nullable=True))
|
||||
op.add_column('users', sa.Column('region_code', sa.String(length=5), server_default='HU', nullable=True))
|
||||
op.drop_constraint(op.f('users_person_id_fkey'), 'users', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'users', 'persons', ['person_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'verification_tokens', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data', ondelete='CASCADE')
|
||||
op.drop_constraint(op.f('wallets_user_id_fkey'), 'wallets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'wallets', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'wallets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('wallets_user_id_fkey'), 'wallets', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'verification_tokens', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', 'users', ['user_id'], ['id'], ondelete='CASCADE')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'])
|
||||
op.drop_constraint(None, 'users', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('users_person_id_fkey'), 'users', 'persons', ['person_id'], ['id'])
|
||||
op.drop_column('users', 'region_code')
|
||||
op.drop_column('users', 'preferred_language')
|
||||
op.drop_constraint(None, 'user_stats', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_stats_user_id_fkey'), 'user_stats', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_badges_user_id_fkey'), 'user_badges', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('user_badges_badge_id_fkey'), 'user_badges', 'badges', ['badge_id'], ['id'])
|
||||
op.drop_constraint(None, 'service_specialties', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('service_specialties_parent_id_fkey'), 'service_specialties', 'service_specialties', ['parent_id'], ['id'])
|
||||
op.drop_constraint(None, 'ratings', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('ratings_author_id_fkey'), 'ratings', 'users', ['author_id'], ['id'])
|
||||
op.drop_constraint(None, 'points_ledger', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('points_ledger_user_id_fkey'), 'points_ledger', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'persons', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('persons_address_id_fkey'), 'persons', 'addresses', ['address_id'], ['id'])
|
||||
op.drop_constraint(None, 'pending_actions', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'pending_actions', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('pending_actions_requester_id_fkey'), 'pending_actions', 'users', ['requester_id'], ['id'])
|
||||
op.create_foreign_key(op.f('pending_actions_approver_id_fkey'), 'pending_actions', 'users', ['approver_id'], ['id'])
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organizations_owner_id_fkey'), 'organizations', 'users', ['owner_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organizations_address_id_fkey'), 'organizations', 'addresses', ['address_id'], ['id'])
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organization_members_organization_id_fkey'), 'organization_members', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organization_members_user_id_fkey'), 'organization_members', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'])
|
||||
op.create_foreign_key(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'geo_streets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
op.drop_constraint(None, 'documents', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('documents_uploaded_by_fkey'), 'documents', 'users', ['uploaded_by'], ['id'])
|
||||
op.drop_constraint(None, 'credit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('credit_logs_org_id_fkey'), 'credit_logs', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'audit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('audit_logs_user_id_fkey'), 'audit_logs', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'assets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('assets_catalog_id_fkey'), 'assets', 'vehicle_catalog', ['catalog_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_telemetry', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_financials', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_financials_asset_id_fkey'), 'asset_financials', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_events', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_events_asset_id_fkey'), 'asset_events', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_costs_driver_id_fkey'), 'asset_costs', 'users', ['driver_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_organization_id_fkey'), 'asset_costs', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_asset_id_fkey'), 'asset_costs', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'addresses', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('addresses_postal_code_id_fkey'), 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,200 @@
|
||||
"""finalize_gamification_v1.5_clean
|
||||
|
||||
Revision ID: 8e06c5386cba
|
||||
Revises: 2cfe9285eb9d
|
||||
Create Date: 2026-02-10 16:18:15.900078
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '8e06c5386cba'
|
||||
down_revision: Union[str, Sequence[str], None] = '2cfe9285eb9d'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(op.f('addresses_postal_code_id_fkey'), 'addresses', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_costs_driver_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_organization_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_asset_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_costs', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'users', ['driver_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_events_asset_id_fkey'), 'asset_events', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_events', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_financials_asset_id_fkey'), 'asset_financials', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_financials', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_telemetry', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('assets_catalog_id_fkey'), 'assets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'assets', 'vehicle_catalog', ['catalog_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('audit_logs_user_id_fkey'), 'audit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'audit_logs', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('credit_logs_org_id_fkey'), 'credit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'credit_logs', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('documents_uploaded_by_fkey'), 'documents', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'documents', 'users', ['uploaded_by'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.drop_constraint(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('organization_members_user_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organization_members_organization_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organization_members', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organization_members', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(op.f('organizations_owner_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organizations_address_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organizations', 'users', ['owner_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organizations', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('persons_address_id_fkey'), 'persons', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'persons', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_column('persons', 'medical_emergency')
|
||||
op.drop_column('persons', 'birth_date')
|
||||
op.drop_column('persons', 'ice_contact')
|
||||
op.drop_column('persons', 'birth_place')
|
||||
op.drop_column('persons', 'mothers_first_name')
|
||||
op.drop_column('persons', 'mothers_last_name')
|
||||
op.add_column('points_ledger', sa.Column('penalty_change', sa.Integer(), server_default=sa.text('0'), nullable=False))
|
||||
op.drop_constraint(op.f('points_ledger_user_id_fkey'), 'points_ledger', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'points_ledger', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('ratings_author_id_fkey'), 'ratings', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'ratings', 'users', ['author_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('service_specialties_parent_id_fkey'), 'service_specialties', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'service_specialties', 'service_specialties', ['parent_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_badges_badge_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.drop_constraint(op.f('user_badges_user_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_badges', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'user_badges', 'badges', ['badge_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.add_column('user_stats', sa.Column('penalty_points', sa.Integer(), server_default=sa.text('0'), nullable=False))
|
||||
op.add_column('user_stats', sa.Column('restriction_level', sa.Integer(), server_default=sa.text('0'), nullable=False))
|
||||
op.drop_constraint(op.f('user_stats_user_id_fkey'), 'user_stats', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_stats', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('users_person_id_fkey'), 'users', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'users', 'persons', ['person_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_column('users', 'region_code')
|
||||
op.drop_column('users', 'preferred_language')
|
||||
op.drop_column('users', 'preferred_currency')
|
||||
op.drop_column('users', 'timezone')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'verification_tokens', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data', ondelete='CASCADE')
|
||||
op.drop_constraint(op.f('wallets_user_id_fkey'), 'wallets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'wallets', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'wallets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('wallets_user_id_fkey'), 'wallets', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'verification_tokens', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', 'users', ['user_id'], ['id'], ondelete='CASCADE')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'])
|
||||
op.add_column('users', sa.Column('timezone', sa.VARCHAR(length=50), autoincrement=False, nullable=True))
|
||||
op.add_column('users', sa.Column('preferred_currency', sa.VARCHAR(length=3), autoincrement=False, nullable=True))
|
||||
op.add_column('users', sa.Column('preferred_language', sa.VARCHAR(length=5), autoincrement=False, nullable=True))
|
||||
op.add_column('users', sa.Column('region_code', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'users', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('users_person_id_fkey'), 'users', 'persons', ['person_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_stats', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_stats_user_id_fkey'), 'user_stats', 'users', ['user_id'], ['id'])
|
||||
op.drop_column('user_stats', 'restriction_level')
|
||||
op.drop_column('user_stats', 'penalty_points')
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_badges_user_id_fkey'), 'user_badges', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('user_badges_badge_id_fkey'), 'user_badges', 'badges', ['badge_id'], ['id'])
|
||||
op.drop_constraint(None, 'service_specialties', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('service_specialties_parent_id_fkey'), 'service_specialties', 'service_specialties', ['parent_id'], ['id'])
|
||||
op.drop_constraint(None, 'ratings', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('ratings_author_id_fkey'), 'ratings', 'users', ['author_id'], ['id'])
|
||||
op.drop_constraint(None, 'points_ledger', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('points_ledger_user_id_fkey'), 'points_ledger', 'users', ['user_id'], ['id'])
|
||||
op.drop_column('points_ledger', 'penalty_change')
|
||||
op.add_column('persons', sa.Column('mothers_last_name', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.add_column('persons', sa.Column('mothers_first_name', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.add_column('persons', sa.Column('birth_place', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.add_column('persons', sa.Column('ice_contact', postgresql.JSON(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), autoincrement=False, nullable=True))
|
||||
op.add_column('persons', sa.Column('birth_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
|
||||
op.add_column('persons', sa.Column('medical_emergency', postgresql.JSON(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'persons', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('persons_address_id_fkey'), 'persons', 'addresses', ['address_id'], ['id'])
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organizations_address_id_fkey'), 'organizations', 'addresses', ['address_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organizations_owner_id_fkey'), 'organizations', 'users', ['owner_id'], ['id'])
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organization_members_organization_id_fkey'), 'organization_members', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organization_members_user_id_fkey'), 'organization_members', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', 'organizations', ['org_id'], ['id'])
|
||||
op.create_foreign_key(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'])
|
||||
op.drop_constraint(None, 'geo_streets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
op.drop_constraint(None, 'documents', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('documents_uploaded_by_fkey'), 'documents', 'users', ['uploaded_by'], ['id'])
|
||||
op.drop_constraint(None, 'credit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('credit_logs_org_id_fkey'), 'credit_logs', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'audit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('audit_logs_user_id_fkey'), 'audit_logs', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'assets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('assets_catalog_id_fkey'), 'assets', 'vehicle_catalog', ['catalog_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_telemetry', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_financials', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_financials_asset_id_fkey'), 'asset_financials', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_events', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_events_asset_id_fkey'), 'asset_events', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_costs_asset_id_fkey'), 'asset_costs', 'assets', ['asset_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_organization_id_fkey'), 'asset_costs', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_driver_id_fkey'), 'asset_costs', 'users', ['driver_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'addresses', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('addresses_postal_code_id_fkey'), 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,174 @@
|
||||
"""add_pending_actions_for_dual_control
|
||||
|
||||
Revision ID: bc5669f12ffd
|
||||
Revises: ffffad1dbe37
|
||||
Create Date: 2026-02-10 17:43:45.357771
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'bc5669f12ffd'
|
||||
down_revision: Union[str, Sequence[str], None] = 'ffffad1dbe37'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(op.f('addresses_postal_code_id_fkey'), 'addresses', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_costs_organization_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_driver_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_asset_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_costs', 'users', ['driver_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_events_asset_id_fkey'), 'asset_events', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_events', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_financials_asset_id_fkey'), 'asset_financials', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_financials', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_telemetry', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('assets_catalog_id_fkey'), 'assets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'assets', 'vehicle_catalog', ['catalog_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('audit_logs_user_id_fkey'), 'audit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'audit_logs', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('credit_logs_org_id_fkey'), 'credit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'credit_logs', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('documents_uploaded_by_fkey'), 'documents', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'documents', 'users', ['uploaded_by'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.drop_constraint(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('organization_members_user_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organization_members_organization_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organization_members', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organization_members', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(op.f('organizations_address_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organizations_owner_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organizations', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organizations', 'users', ['owner_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('persons_address_id_fkey'), 'persons', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'persons', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('points_ledger_user_id_fkey'), 'points_ledger', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'points_ledger', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('ratings_author_id_fkey'), 'ratings', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'ratings', 'users', ['author_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('service_specialties_parent_id_fkey'), 'service_specialties', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'service_specialties', 'service_specialties', ['parent_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_badges_badge_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.drop_constraint(op.f('user_badges_user_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_badges', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'user_badges', 'badges', ['badge_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_stats_user_id_fkey'), 'user_stats', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_stats', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('users_person_id_fkey'), 'users', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'users', 'persons', ['person_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'verification_tokens', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data', ondelete='CASCADE')
|
||||
op.drop_constraint(op.f('wallets_user_id_fkey'), 'wallets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'wallets', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'wallets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('wallets_user_id_fkey'), 'wallets', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'verification_tokens', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', 'users', ['user_id'], ['id'], ondelete='CASCADE')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'])
|
||||
op.drop_constraint(None, 'users', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('users_person_id_fkey'), 'users', 'persons', ['person_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_stats', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_stats_user_id_fkey'), 'user_stats', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_badges_user_id_fkey'), 'user_badges', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('user_badges_badge_id_fkey'), 'user_badges', 'badges', ['badge_id'], ['id'])
|
||||
op.drop_constraint(None, 'service_specialties', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('service_specialties_parent_id_fkey'), 'service_specialties', 'service_specialties', ['parent_id'], ['id'])
|
||||
op.drop_constraint(None, 'ratings', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('ratings_author_id_fkey'), 'ratings', 'users', ['author_id'], ['id'])
|
||||
op.drop_constraint(None, 'points_ledger', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('points_ledger_user_id_fkey'), 'points_ledger', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'persons', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('persons_address_id_fkey'), 'persons', 'addresses', ['address_id'], ['id'])
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organizations_owner_id_fkey'), 'organizations', 'users', ['owner_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organizations_address_id_fkey'), 'organizations', 'addresses', ['address_id'], ['id'])
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organization_members_organization_id_fkey'), 'organization_members', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organization_members_user_id_fkey'), 'organization_members', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'])
|
||||
op.create_foreign_key(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'geo_streets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
op.drop_constraint(None, 'documents', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('documents_uploaded_by_fkey'), 'documents', 'users', ['uploaded_by'], ['id'])
|
||||
op.drop_constraint(None, 'credit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('credit_logs_org_id_fkey'), 'credit_logs', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'audit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('audit_logs_user_id_fkey'), 'audit_logs', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'assets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('assets_catalog_id_fkey'), 'assets', 'vehicle_catalog', ['catalog_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_telemetry', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_financials', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_financials_asset_id_fkey'), 'asset_financials', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_events', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_events_asset_id_fkey'), 'asset_events', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_costs_asset_id_fkey'), 'asset_costs', 'assets', ['asset_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_driver_id_fkey'), 'asset_costs', 'users', ['driver_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_organization_id_fkey'), 'asset_costs', 'organizations', ['organization_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'addresses', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('addresses_postal_code_id_fkey'), 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,192 @@
|
||||
"""upgrade_audit_log_for_security
|
||||
|
||||
Revision ID: ffffad1dbe37
|
||||
Revises: 8e06c5386cba
|
||||
Create Date: 2026-02-10 17:33:17.436161
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'ffffad1dbe37'
|
||||
down_revision: Union[str, Sequence[str], None] = '8e06c5386cba'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(op.f('addresses_postal_code_id_fkey'), 'addresses', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_assignments', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_costs_driver_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_asset_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_costs_organization_id_fkey'), 'asset_costs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_costs', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'users', ['driver_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_costs', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_events_asset_id_fkey'), 'asset_events', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_events', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_financials_asset_id_fkey'), 'asset_financials', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_financials', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.drop_constraint(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'asset_reviews', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'asset_telemetry', 'assets', ['asset_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('assets_catalog_id_fkey'), 'assets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'assets', 'vehicle_catalog', ['catalog_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.add_column('audit_logs', sa.Column('severity', sa.Enum('info', 'warning', 'critical', 'emergency', name='logseverity'), nullable=False))
|
||||
op.add_column('audit_logs', sa.Column('old_data', sa.JSON(), nullable=True))
|
||||
op.add_column('audit_logs', sa.Column('new_data', sa.JSON(), nullable=True))
|
||||
op.add_column('audit_logs', sa.Column('ip_address', sa.String(length=45), nullable=True))
|
||||
op.add_column('audit_logs', sa.Column('user_agent', sa.Text(), nullable=True))
|
||||
op.create_index(op.f('ix_data_audit_logs_action'), 'audit_logs', ['action'], unique=False, schema='data')
|
||||
op.create_index(op.f('ix_data_audit_logs_ip_address'), 'audit_logs', ['ip_address'], unique=False, schema='data')
|
||||
op.create_index(op.f('ix_data_audit_logs_timestamp'), 'audit_logs', ['timestamp'], unique=False, schema='data')
|
||||
op.drop_constraint(op.f('audit_logs_user_id_fkey'), 'audit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'audit_logs', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_column('audit_logs', 'changes')
|
||||
op.drop_constraint(op.f('credit_logs_org_id_fkey'), 'credit_logs', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'credit_logs', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('documents_uploaded_by_fkey'), 'documents', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'documents', 'users', ['uploaded_by'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.drop_constraint(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'organizations', ['org_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('organization_members_user_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organization_members_organization_id_fkey'), 'organization_members', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organization_members', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organization_members', 'organizations', ['organization_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(op.f('organizations_address_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.drop_constraint(op.f('organizations_owner_id_fkey'), 'organizations', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'organizations', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'organizations', 'users', ['owner_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('persons_address_id_fkey'), 'persons', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'persons', 'addresses', ['address_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('points_ledger_user_id_fkey'), 'points_ledger', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'points_ledger', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('ratings_author_id_fkey'), 'ratings', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'ratings', 'users', ['author_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('service_specialties_parent_id_fkey'), 'service_specialties', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'service_specialties', 'service_specialties', ['parent_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_badges_user_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.drop_constraint(op.f('user_badges_badge_id_fkey'), 'user_badges', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_badges', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'user_badges', 'badges', ['badge_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('user_stats_user_id_fkey'), 'user_stats', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'user_stats', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('users_person_id_fkey'), 'users', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'users', 'persons', ['person_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.drop_constraint(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.create_foreign_key(None, 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
op.drop_constraint(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'verification_tokens', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data', ondelete='CASCADE')
|
||||
op.drop_constraint(op.f('wallets_user_id_fkey'), 'wallets', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'wallets', 'users', ['user_id'], ['id'], source_schema='data', referent_schema='data')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'wallets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('wallets_user_id_fkey'), 'wallets', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'verification_tokens', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('verification_tokens_user_id_fkey'), 'verification_tokens', 'users', ['user_id'], ['id'], ondelete='CASCADE')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'vehicle_ownerships', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_user_id_fkey'), 'vehicle_ownerships', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('vehicle_ownerships_vehicle_id_fkey'), 'vehicle_ownerships', 'assets', ['vehicle_id'], ['id'])
|
||||
op.drop_constraint(None, 'users', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('users_person_id_fkey'), 'users', 'persons', ['person_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_stats', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_stats_user_id_fkey'), 'user_stats', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'user_badges', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('user_badges_badge_id_fkey'), 'user_badges', 'badges', ['badge_id'], ['id'])
|
||||
op.create_foreign_key(op.f('user_badges_user_id_fkey'), 'user_badges', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'service_specialties', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('service_specialties_parent_id_fkey'), 'service_specialties', 'service_specialties', ['parent_id'], ['id'])
|
||||
op.drop_constraint(None, 'ratings', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('ratings_author_id_fkey'), 'ratings', 'users', ['author_id'], ['id'])
|
||||
op.drop_constraint(None, 'points_ledger', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('points_ledger_user_id_fkey'), 'points_ledger', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'persons', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('persons_address_id_fkey'), 'persons', 'addresses', ['address_id'], ['id'])
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organizations', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organizations_owner_id_fkey'), 'organizations', 'users', ['owner_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organizations_address_id_fkey'), 'organizations', 'addresses', ['address_id'], ['id'])
|
||||
op.alter_column('organizations', 'org_type',
|
||||
existing_type=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype', schema='data', inherit_schema=True),
|
||||
type_=postgresql.ENUM('individual', 'service', 'service_provider', 'fleet_owner', 'club', 'business', name='orgtype'),
|
||||
existing_nullable=True)
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'organization_members', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('organization_members_organization_id_fkey'), 'organization_members', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('organization_members_user_id_fkey'), 'organization_members', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'org_subscriptions', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('org_subscriptions_tier_id_fkey'), 'org_subscriptions', 'subscription_tiers', ['tier_id'], ['id'])
|
||||
op.create_foreign_key(op.f('org_subscriptions_org_id_fkey'), 'org_subscriptions', 'organizations', ['org_id'], ['id'])
|
||||
op.drop_constraint(None, 'geo_streets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('geo_streets_postal_code_id_fkey'), 'geo_streets', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
op.drop_constraint(None, 'documents', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('documents_uploaded_by_fkey'), 'documents', 'users', ['uploaded_by'], ['id'])
|
||||
op.drop_constraint(None, 'credit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('credit_logs_org_id_fkey'), 'credit_logs', 'organizations', ['org_id'], ['id'])
|
||||
op.add_column('audit_logs', sa.Column('changes', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'audit_logs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('audit_logs_user_id_fkey'), 'audit_logs', 'users', ['user_id'], ['id'])
|
||||
op.drop_index(op.f('ix_data_audit_logs_timestamp'), table_name='audit_logs', schema='data')
|
||||
op.drop_index(op.f('ix_data_audit_logs_ip_address'), table_name='audit_logs', schema='data')
|
||||
op.drop_index(op.f('ix_data_audit_logs_action'), table_name='audit_logs', schema='data')
|
||||
op.drop_column('audit_logs', 'user_agent')
|
||||
op.drop_column('audit_logs', 'ip_address')
|
||||
op.drop_column('audit_logs', 'new_data')
|
||||
op.drop_column('audit_logs', 'old_data')
|
||||
op.drop_column('audit_logs', 'severity')
|
||||
op.drop_constraint(None, 'assets', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('assets_catalog_id_fkey'), 'assets', 'vehicle_catalog', ['catalog_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_telemetry', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_telemetry_asset_id_fkey'), 'asset_telemetry', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_reviews', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_reviews_user_id_fkey'), 'asset_reviews', 'users', ['user_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_reviews_asset_id_fkey'), 'asset_reviews', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_financials', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_financials_asset_id_fkey'), 'asset_financials', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_events', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_events_asset_id_fkey'), 'asset_events', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_costs', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_costs_organization_id_fkey'), 'asset_costs', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_asset_id_fkey'), 'asset_costs', 'assets', ['asset_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_costs_driver_id_fkey'), 'asset_costs', 'users', ['driver_id'], ['id'])
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.drop_constraint(None, 'asset_assignments', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('asset_assignments_organization_id_fkey'), 'asset_assignments', 'organizations', ['organization_id'], ['id'])
|
||||
op.create_foreign_key(op.f('asset_assignments_asset_id_fkey'), 'asset_assignments', 'assets', ['asset_id'], ['id'])
|
||||
op.drop_constraint(None, 'addresses', schema='data', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('addresses_postal_code_id_fkey'), 'addresses', 'geo_postal_codes', ['postal_code_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
23
backend/static/locales/en.json
Normal file
23
backend/static/locales/en.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"AUTH": {
|
||||
"LOGIN": {
|
||||
"SUCCESS": "Login successful. Welcome back!"
|
||||
},
|
||||
"ERROR": {
|
||||
"EMAIL_EXISTS": "This email is already registered."
|
||||
}
|
||||
},
|
||||
"SENTINEL": {
|
||||
"LOCK": {
|
||||
"MSG": "Account locked for security reasons."
|
||||
}
|
||||
},
|
||||
"EMAIL": {
|
||||
"REG": {
|
||||
"SUBJECT": "Confirm your registration"
|
||||
},
|
||||
"PWD_RESET": {
|
||||
"SUBJECT": "Password Reset Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
31
backend/static/locales/hu.json
Normal file
31
backend/static/locales/hu.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"AUTH": {
|
||||
"LOGIN": {
|
||||
"SUCCESS": "Sikeres bejelentkezés. Üdvözlünk ismét!"
|
||||
},
|
||||
"ERROR": {
|
||||
"EMAIL_EXISTS": "Ez az e-mail cím már foglalt.",
|
||||
"UNAUTHORIZED": "Nincs jogosultságod a művelethez."
|
||||
}
|
||||
},
|
||||
"SENTINEL": {
|
||||
"LOCK": {
|
||||
"MSG": "A fiók biztonsági okokból zárolva lett."
|
||||
},
|
||||
"APPROVAL": {
|
||||
"REQUIRED": "A művelet végrehajtásához egy másik admin jóváhagyása szükséges."
|
||||
}
|
||||
},
|
||||
"EMAIL": {
|
||||
"REG": {
|
||||
"SUBJECT": "Regisztráció megerősítése - Service Finder",
|
||||
"GREETING": "Szia {{first_name}}! Kattints a linkre a flottád aktiválásához: {{link}}"
|
||||
},
|
||||
"PWD_RESET": {
|
||||
"SUBJECT": "Jelszó visszaállítás"
|
||||
}
|
||||
},
|
||||
"COMMON": {
|
||||
"SAVE_SUCCESS": "Sikeres mentés!"
|
||||
}
|
||||
}
|
||||
@@ -102,3 +102,46 @@ Minden érték (szorzók, határok) a \`GAMIFICATION_MASTER_CONFIG\` JSON param
|
||||
|
||||
### 3. Audit
|
||||
Minden pontmozgás a \`PointsLedger\` táblába kerül rögzítésre a visszakövethetőség érdekében.
|
||||
|
||||
XP Formula: $XP_{required} = BaseXP \times Level^{1.5}$Penalty Logic: restriction_level bevezetése (0-3).Weighting: Saját adat vs. Közösségi adat súlyozási táblázata.
|
||||
|
||||
# 11. Gamification és Social Engine Specifikáció
|
||||
|
||||
## 1. XP (Experience Points) - A Tekintély
|
||||
Az XP a felhasználó végleges, nem csökkenthető tekintélypontja.
|
||||
- **Képlet:** A szintlépéshez szükséges összes XP:
|
||||
$$XP_{total} = 500 \times Level^{1.5}$$
|
||||
- **Súlyozás:**
|
||||
- **Saját adat (Fleet):** Alacsony érték (pl. 10 XP).
|
||||
- **Közösségi adat (Service Discovery):** Magas érték (pl. 100 XP).
|
||||
|
||||
## 2. Social Points - A Valuta Alapja
|
||||
Szezonális pontok, amelyek Kreditre válthatóak.
|
||||
- **Váltószám:** Alapértelmezett: 100 Social Point = 1 Kredit.
|
||||
- **Váltási mód:** Automatikus (rendszerparaméter alapján) vagy manuális (felhasználói döntés).
|
||||
|
||||
## 3. Trust & Penalty Engine (Büntetőrendszer)
|
||||
A rendszer integritásának védelme érdekében hibapontokat (Penalty Points) alkalmazunk.
|
||||
- **Szintek (Restriction Level):**
|
||||
- **0 (Normal):** Teljes pontszorzó (1.0x).
|
||||
- **1 (Warning):** Csökkentett pontszerzés (0.5x).
|
||||
- **2 (Restricted):** Szigorú moderátori ellenőrzés minden adatnál, 0.1x pontszerzés.
|
||||
- **3 (Blocked):** Pontszerzés és adatbeküldés tiltva.
|
||||
- **Ledolgozás:** Minden pozitív XP szerzés a büntetőpontokat is csökkenti (pl. 1 XP jóváírás = 0.5 Penalty pont levonás).
|
||||
|
||||
## 4. Szintlépési Bónuszok
|
||||
Minden 10. szint elérésekor a rendszer automatikus Kredit jutalmat oszt a `GAMIFICATION_MASTER_CONFIG` alapján.
|
||||
|
||||
## 5. Büntetőrendszer (Strike System)
|
||||
A rendszer integritásának megőrzése érdekében hibapontokat alkalmazunk, amelyek befolyásolják a pontszerzés hatékonyságát.
|
||||
|
||||
- **Szorzók (Multipliers):**
|
||||
- Level 0 (Normal): 1.0x
|
||||
- Level 1 (Warning): 0.5x
|
||||
- Level 2 (Restricted): 0.1x
|
||||
- Level 3 (Blocked): 0.0x
|
||||
|
||||
- **Ledolgozás (Recovery):**
|
||||
A büntetőpontok pozitív aktivitással (XP szerzéssel) ledolgozhatóak. Az elért XP egy admin által meghatározott része (alapértelmezett: 50%) levonásra kerül a büntetőpontokból.
|
||||
|
||||
- **Admin-Vezérelt Küszöbök:** Minden szintváltási határ a `GAMIFICATION_MASTER_CONFIG` paraméterben definiált.
|
||||
@@ -250,3 +250,40 @@ A rendszer most már képes egyetlen KYC folyamat alatt aktiválni a felhasznál
|
||||
### Changed
|
||||
- `AssetCost` modell mezőnevek szinkronizálva a pénzügyi standardokhoz (`amount_local`, `amount_eur`).
|
||||
- `SystemParameter` modell elnevezés igazítva a meglévő adatbázis sémához.
|
||||
|
||||
## [1.5.0] - 2026-02-10
|
||||
|
||||
### Added
|
||||
- **Judge & Penalty System**: Bevezetve a `penalty_points` és `restriction_level` mechanizmus a visszaélések kiszűrésére.
|
||||
- **Dynamic Multipliers**: Admin felületről (JSON config) állítható pontszorzók a büntetési szintekhez.
|
||||
- **Social-to-Credit Auto-conversion**: Automatikus Kredit jóváírás a Walletbe meghatározott Social pont elérésekor.
|
||||
- **Level Achievement Bonus**: 10-es szintenkénti automatikus Kredit jutalmazás.
|
||||
|
||||
### Fixed
|
||||
- **Circular Dependency Fix**: A modellek közötti import hurok végleges felszámolva (string-alapú relationship hivatkozások).
|
||||
- **Identity Schema Protection**: Visszaállítva a `User` modell hiányzó `scope_id`, `scope_level` és `custom_permissions` mezői.
|
||||
- **Database Consistency**: A `user_stats` és `asset_costs` táblák sikeresen migráltak a NOT NULL kényszerek és alapértelmezett értékek (server_default) beállításával.
|
||||
|
||||
### Changed
|
||||
- **GamificationService**: Mostantól központi "Bíróként" funkcionál, leválasztva a pontszámítási logikát a többi szervizről.
|
||||
- **Identity Model**: A `Wallet` és `VerificationToken` osztályok integrálva az `identity.py` modulba.
|
||||
|
||||
# Changelog - Service Finder Backend
|
||||
**Verzió:** 1.6.0 (Sentinel & i18n Update)
|
||||
**Dátum:** 2026.02.10.
|
||||
|
||||
## [1.6.0] - 2026-02-10
|
||||
### Hozzáadva
|
||||
- **Sentinel Biztonsági Rendszer:** - `PendingAction` modell bevezetése a "Négy szem elv" (Dual Control) biztosításához.
|
||||
- `SecurityService` implementálása: Adatlopás elleni védelem (Throttling) és automata vészleállító (Emergency Lock).
|
||||
- `AuditLog` bővítése szigorúbb súlyossági szintekkel (`critical`, `emergency`).
|
||||
- **Nyelvi Modul (i18n):**
|
||||
- `Translation` modell a `data` sémában.
|
||||
- `TranslationService`: Adatbázis-alapú fordításkezelés, szerveroldali cache, Fallback (EN) logika és JSON export funkció a Frontend számára.
|
||||
- **Admin Kontroll Panel:**
|
||||
- Új API végpontok a függőben lévő műveletek jóváhagyásához, a rendszerbiztonsági állapot monitorozásához és a nyelvi szinkronizációhoz.
|
||||
|
||||
### Javítva
|
||||
- **Circular Import Fix:** A modellek importálási rendjének optimalizálása a `app.db.base_class` közvetlen használatával, megszüntetve a hurok-importokat.
|
||||
- **Függőségkezelés:** `deps.py` bővítve a `get_current_active_user` függőséggel a biztonsági zárolások érvényesítéséhez.
|
||||
- **Soft-Delete Logika:** A felhasználói fiók törlése mostantól felszabadítja az eredeti e-mail címet a TWINS-elvű újra-regisztrációhoz.
|
||||
@@ -103,3 +103,19 @@ A rendszer a \`system_parameters\` táblában tárolt \`RBAC_MASTER_CONFIG\` JSO
|
||||
Minden műveletnél ellenőrizzük a \`scope_id\` egyezését:
|
||||
- Ha a felhasználó \`scope_level = 'region'\`, akkor csak olyan adatot szerkeszthet, ami ugyanahhoz a régióhoz tartozik.
|
||||
- Kivétel: Impersonation (Megszemélyesítés) - Audit loggal védve.
|
||||
|
||||
|
||||
## 1. Gamification Adminisztráció
|
||||
A `data.system_parameters` táblában a `GAMIFICATION_MASTER_CONFIG` kulcs alatt az alábbiak állíthatóak:
|
||||
- `xp_logic`: `base_xp`, `exponent`.
|
||||
- `penalty_thresholds`: A szintekhez tartozó büntetőpont határok.
|
||||
- `level_up_rewards`: 10-es szintenkénti Kredit jutalom mértéke.
|
||||
- `blocked_roles`: [superadmin, service_bot].
|
||||
- `auto_convert_social`: True/False.
|
||||
|
||||
## 2. Gamification Konfiguráció (JSON Schema)
|
||||
A `GAMIFICATION_MASTER_CONFIG` struktúrája:
|
||||
- `xp_logic`: Alap XP és kitevő a nehezedő szintezéshez.
|
||||
- `penalty_logic`: Küszöbértékek, szorzók és ledolgozási ráta.
|
||||
- `conversion_logic`: Social-to-Credit váltási arány.
|
||||
- `level_rewards`: Szintlépési bónuszok mértéke.
|
||||
Reference in New Issue
Block a user