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(
|
async def get_current_token_payload(
|
||||||
token: str = Depends(reusable_oauth2)
|
token: str = Depends(reusable_oauth2)
|
||||||
) -> Dict[str, Any]:
|
) -> 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":
|
if token == "dev_bypass_active":
|
||||||
return {
|
return {
|
||||||
"sub": "1",
|
"sub": "1",
|
||||||
@@ -40,9 +36,6 @@ async def get_current_user(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
payload: Dict[str, Any] = Depends(get_current_token_payload),
|
payload: Dict[str, Any] = Depends(get_current_token_payload),
|
||||||
) -> User:
|
) -> User:
|
||||||
"""
|
|
||||||
Visszaadja a teljes User modellt. Akkor használjuk, ha módosítani kell az usert.
|
|
||||||
"""
|
|
||||||
user_id = payload.get("sub")
|
user_id = payload.get("sub")
|
||||||
if not user_id:
|
if not user_id:
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token azonosítási hiba.")
|
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
|
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):
|
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)):
|
def rank_checker(payload: Dict[str, Any] = Depends(get_current_token_payload)):
|
||||||
user_rank = payload.get("rank", 0)
|
user_rank = payload.get("rank", 0)
|
||||||
if user_rank < required_rank:
|
if user_rank < required_rank:
|
||||||
|
|||||||
Binary file not shown.
@@ -1,22 +1,26 @@
|
|||||||
from fastapi import APIRouter
|
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()
|
api_router = APIRouter()
|
||||||
|
|
||||||
# Hitelesítés
|
# Hitelesítés (Authentication)
|
||||||
api_router.include_router(auth.router, prefix="/auth", tags=["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"])
|
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"])
|
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"])
|
api_router.include_router(assets.router, prefix="/assets", tags=["Assets"])
|
||||||
|
|
||||||
# Szervezetek
|
# Szervezetek (Organizations)
|
||||||
api_router.include_router(organizations.router, prefix="/organizations", tags=["Organizations"])
|
api_router.include_router(organizations.router, prefix="/organizations", tags=["Organizations"])
|
||||||
|
|
||||||
# Dokumentumok
|
# Dokumentumok (Documents)
|
||||||
api_router.include_router(documents.router, prefix="/documents", tags=["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 fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select, func
|
||||||
from typing import List
|
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.api import deps
|
||||||
from app.models.user import User, UserRole
|
from app.models.identity import User, UserRole
|
||||||
from app.models.system_settings import SystemSetting # ÚJ import
|
from app.models.system_config import SystemParameter
|
||||||
from app.models.gamification import PointRule, LevelConfig, RegionalSetting
|
from app.models.security import PendingAction, ActionStatus
|
||||||
from app.models.translation import Translation
|
from app.models.history import AuditLog, LogSeverity
|
||||||
from app.services.translation_service import TranslationService
|
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()
|
router = APIRouter()
|
||||||
|
|
||||||
def check_admin_access(current_user: User, required_roles: List[UserRole]):
|
# --- 🛡️ ADMIN JOGOSULTSÁG ELLENŐRZŐ ---
|
||||||
if current_user.role not in required_roles:
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
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])
|
@router.get("/pending-actions", response_model=List[PendingActionResponse])
|
||||||
async def get_all_system_settings(
|
async def list_pending_actions(
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(deps.get_db),
|
||||||
current_user: User = Depends(deps.get_current_user)
|
admin: User = Depends(check_admin_access)
|
||||||
):
|
):
|
||||||
"""Az összes globális rendszerbeállítás listázása."""
|
"""Jóváhagyásra váró kritikus kérések listázása."""
|
||||||
check_admin_access(current_user, [UserRole.SUPERUSER])
|
stmt = select(PendingAction).where(PendingAction.status == ActionStatus.pending)
|
||||||
result = await db.execute(select(SystemSetting))
|
result = await db.execute(stmt)
|
||||||
settings = result.scalars().all()
|
return result.scalars().all()
|
||||||
return [{"key": s.key, "value": s.value, "description": s.description} for s in settings]
|
|
||||||
|
@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}")
|
@router.put("/settings/{key}")
|
||||||
async def update_system_setting(
|
async def update_setting(key: str, value: Any, db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
|
||||||
key: str,
|
"""Paraméter módosítása és Audit Log generálása."""
|
||||||
new_value: int, # Később lehet JSON is, ha komplexebb a beállítás
|
stmt = select(SystemParameter).where(SystemParameter.key == key)
|
||||||
db: AsyncSession = Depends(get_db),
|
param = (await db.execute(stmt)).scalar_one_or_none()
|
||||||
current_user: User = Depends(deps.get_current_user)
|
if not param:
|
||||||
):
|
raise HTTPException(status_code=404, detail="Nincs ilyen beállítás.")
|
||||||
"""Egy adott beállítás (pl. FREE_VEHICLE_LIMIT) módosítása."""
|
|
||||||
check_admin_access(current_user, [UserRole.SUPERUSER])
|
|
||||||
|
|
||||||
result = await db.execute(select(SystemSetting).where(SystemSetting.key == key))
|
old_val = param.value
|
||||||
setting = result.scalar_one_or_none()
|
param.value = value
|
||||||
|
|
||||||
if not setting:
|
await security_service.log_event(
|
||||||
raise HTTPException(status_code=404, detail="Beállítás nem található")
|
db, admin.id, action="SETTING_CHANGE", severity=LogSeverity.warning,
|
||||||
|
old_data={key: old_val}, new_data={key: value}
|
||||||
setting.value = new_value
|
)
|
||||||
await db.commit()
|
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/sync")
|
||||||
|
async def sync_translations_to_json(
|
||||||
@router.post("/translations", status_code=status.HTTP_201_CREATED)
|
db: AsyncSession = Depends(deps.get_db),
|
||||||
async def add_translation_draft(
|
admin: User = Depends(check_admin_access)
|
||||||
key: str, lang: str, value: str,
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
current_user: User = Depends(deps.get_current_user)
|
|
||||||
):
|
):
|
||||||
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
|
"""Szinkronizálja az adatbázisban tárolt fordításokat a JSON fájlokba."""
|
||||||
new_t = Translation(key=key, lang_code=lang, value=value, is_published=False)
|
# A TranslationService-ben kell megírni a fájlbaíró logikát
|
||||||
db.add(new_t)
|
await TranslationService.export_to_json(db)
|
||||||
await db.commit()
|
return {"message": "JSON nyelvi fájlok frissítve."}
|
||||||
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."}
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -1,10 +1,16 @@
|
|||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
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 ---
|
# --- General ---
|
||||||
PROJECT_NAME: str = "Traffic Ecosystem SuperApp"
|
PROJECT_NAME: str = "Traffic Ecosystem SuperApp"
|
||||||
VERSION: str = "1.0.0"
|
VERSION: str = "1.0.0"
|
||||||
@@ -16,8 +22,7 @@ class Settings(BaseSettings):
|
|||||||
ALGORITHM: str = "HS256"
|
ALGORITHM: str = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 nap
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 nap
|
||||||
|
|
||||||
# --- Initial Admin (ÚJ SZEKCIÓ) ---
|
# --- Initial Admin ---
|
||||||
# Ezeket a .env-ből fogja venni
|
|
||||||
INITIAL_ADMIN_EMAIL: str = "admin@servicefinder.hu"
|
INITIAL_ADMIN_EMAIL: str = "admin@servicefinder.hu"
|
||||||
INITIAL_ADMIN_PASSWORD: str = "Admin123!"
|
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.system_config import SystemParameter # noqa
|
||||||
from app.models.history import AuditLog, VehicleOwnership # noqa
|
from app.models.history import AuditLog, VehicleOwnership # noqa
|
||||||
from app.models.document import Document # noqa
|
from app.models.document import Document # noqa
|
||||||
|
from app.models.translation import Translation # noqa <--- HOZZÁADVA
|
||||||
from app.models.core_logic import ( # noqa
|
from app.models.core_logic import ( # noqa
|
||||||
SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
|
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 .gamification import PointRule, LevelConfig, UserStats, Badge, UserBadge, Rating, PointsLedger
|
||||||
from .system_config import SystemParameter
|
from .system_config import SystemParameter
|
||||||
from .document import Document
|
from .document import Document
|
||||||
|
from .translation import Translation # <--- HOZZÁADVA
|
||||||
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
|
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
|
||||||
from .history import AuditLog, VehicleOwnership
|
from .history import AuditLog, VehicleOwnership
|
||||||
|
from .security import PendingAction # <--- HOZZÁADVA
|
||||||
|
|
||||||
# Aliasok
|
# Aliasok
|
||||||
Vehicle = Asset
|
Vehicle = Asset
|
||||||
@@ -26,7 +28,8 @@ __all__ = [
|
|||||||
"AssetEvent", "AssetFinancials", "AssetTelemetry", "AssetReview", "ExchangeRate",
|
"AssetEvent", "AssetFinancials", "AssetTelemetry", "AssetReview", "ExchangeRate",
|
||||||
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "PointRule",
|
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "PointRule",
|
||||||
"LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger",
|
"LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger",
|
||||||
"SystemParameter", "Document", "SubscriptionTier", "OrganizationSubscription",
|
"SystemParameter", "Document", "Translation", "PendingAction", # <--- BŐVÍTVE
|
||||||
|
"SubscriptionTier", "OrganizationSubscription",
|
||||||
"CreditTransaction", "ServiceSpecialty", "AuditLog", "VehicleOwnership",
|
"CreditTransaction", "ServiceSpecialty", "AuditLog", "VehicleOwnership",
|
||||||
"Vehicle", "UserVehicle", "VehicleCatalog", "ServiceRecord"
|
"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"))
|
criteria_scores = Column(JSON, server_default=text("'{}'::jsonb"))
|
||||||
comment = Column(Text)
|
comment = Column(Text)
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
asset = relationship("Asset", back_populates="reviews")
|
asset = relationship("Asset", back_populates="reviews")
|
||||||
|
user = relationship("User") # <--- JAVÍTÁS: Hozzáadva
|
||||||
|
|
||||||
class AssetAssignment(Base):
|
class AssetAssignment(Base):
|
||||||
__tablename__ = "asset_assignments"
|
__tablename__ = "asset_assignments"
|
||||||
@@ -86,7 +88,9 @@ class AssetAssignment(Base):
|
|||||||
assigned_at = Column(DateTime(timezone=True), server_default=func.now())
|
assigned_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
released_at = Column(DateTime(timezone=True), nullable=True)
|
released_at = Column(DateTime(timezone=True), nullable=True)
|
||||||
status = Column(String(30), default="active")
|
status = Column(String(30), default="active")
|
||||||
|
|
||||||
asset = relationship("Asset", back_populates="assignments")
|
asset = relationship("Asset", back_populates="assignments")
|
||||||
|
organization = relationship("Organization") # <--- KRITIKUS JAVÍTÁS: Ez okozta a login hibát
|
||||||
|
|
||||||
class AssetEvent(Base):
|
class AssetEvent(Base):
|
||||||
__tablename__ = "asset_events"
|
__tablename__ = "asset_events"
|
||||||
@@ -115,7 +119,10 @@ class AssetCost(Base):
|
|||||||
date = Column(DateTime(timezone=True), server_default=func.now())
|
date = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
mileage_at_cost = Column(Integer)
|
mileage_at_cost = Column(Integer)
|
||||||
data = Column(JSON, server_default=text("'{}'::jsonb"))
|
data = Column(JSON, server_default=text("'{}'::jsonb"))
|
||||||
|
|
||||||
asset = relationship("Asset", back_populates="costs")
|
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):
|
class ExchangeRate(Base):
|
||||||
__tablename__ = "exchange_rates"
|
__tablename__ = "exchange_rates"
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, TYPE_CHECKING
|
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.orm import Mapped, mapped_column, relationship
|
||||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||||
from app.db.base_class import Base
|
from app.db.base_class import Base
|
||||||
|
|
||||||
# Típusvizsgálathoz a körkörös import elkerülése érdekében
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from app.models.identity import User
|
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"}
|
SCHEMA_ARGS = {"schema": "data"}
|
||||||
|
|
||||||
class PointRule(Base):
|
class PointRule(Base):
|
||||||
@@ -30,39 +29,36 @@ class LevelConfig(Base):
|
|||||||
min_points: Mapped[int] = mapped_column(Integer)
|
min_points: Mapped[int] = mapped_column(Integer)
|
||||||
rank_name: Mapped[str] = mapped_column(String)
|
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):
|
class PointsLedger(Base):
|
||||||
__tablename__ = "points_ledger"
|
__tablename__ = "points_ledger"
|
||||||
__table_args__ = SCHEMA_ARGS
|
__table_args__ = SCHEMA_ARGS
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.users.id"))
|
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)
|
reason: Mapped[str] = mapped_column(String)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=func.now())
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=func.now())
|
||||||
|
|
||||||
# Kapcsolat a felhasználóhoz
|
|
||||||
user: Mapped["User"] = relationship("User")
|
user: Mapped["User"] = relationship("User")
|
||||||
|
|
||||||
class UserStats(Base):
|
class UserStats(Base):
|
||||||
__tablename__ = "user_stats"
|
__tablename__ = "user_stats"
|
||||||
__table_args__ = SCHEMA_ARGS
|
__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)
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.users.id"), primary_key=True)
|
||||||
total_xp: Mapped[int] = mapped_column(Integer, default=0)
|
total_xp: Mapped[int] = mapped_column(Integer, default=0)
|
||||||
social_points: 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)
|
current_level: Mapped[int] = mapped_column(Integer, default=1)
|
||||||
|
|
||||||
|
# --- BÜNTETŐ RENDSZER (Strike System) ---
|
||||||
|
# JAVÍTÁS: server_default hozzáadva, hogy a meglévő sorok is 0-t kapjanak
|
||||||
|
penalty_points: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
|
||||||
|
restriction_level: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
|
||||||
|
|
||||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
|
updated_at: Mapped[datetime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
|
||||||
|
|
||||||
# EZ A JAVÍTÁS: A visszamutató kapcsolat definiálása
|
|
||||||
user: Mapped["User"] = relationship("User", back_populates="stats")
|
user: Mapped["User"] = relationship("User", back_populates="stats")
|
||||||
|
|
||||||
|
|
||||||
class Badge(Base):
|
class Badge(Base):
|
||||||
__tablename__ = "badges"
|
__tablename__ = "badges"
|
||||||
__table_args__ = SCHEMA_ARGS
|
__table_args__ = SCHEMA_ARGS
|
||||||
@@ -81,7 +77,7 @@ class UserBadge(Base):
|
|||||||
|
|
||||||
user: Mapped["User"] = relationship("User")
|
user: Mapped["User"] = relationship("User")
|
||||||
|
|
||||||
class Rating(Base): # <--- Az új értékelési modell
|
class Rating(Base):
|
||||||
__tablename__ = "ratings"
|
__tablename__ = "ratings"
|
||||||
__table_args__ = SCHEMA_ARGS
|
__table_args__ = SCHEMA_ARGS
|
||||||
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
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.orm import relationship
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||||
from app.db.base_class import Base
|
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):
|
class VehicleOwnership(Base):
|
||||||
__tablename__ = "vehicle_ownerships"
|
__tablename__ = "vehicle_ownerships"
|
||||||
__table_args__ = {"schema": "data"}
|
__table_args__ = {"schema": "data"}
|
||||||
@@ -20,11 +27,25 @@ class VehicleOwnership(Base):
|
|||||||
class AuditLog(Base):
|
class AuditLog(Base):
|
||||||
__tablename__ = "audit_logs"
|
__tablename__ = "audit_logs"
|
||||||
__table_args__ = {"schema": "data"}
|
__table_args__ = {"schema": "data"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
user_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
|
user_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
|
||||||
target_type = Column(String, index=True)
|
severity = Column(Enum(LogSeverity), default=LogSeverity.info, nullable=False)
|
||||||
target_id = Column(String, index=True)
|
|
||||||
action = Column(String, nullable=False)
|
# Mi történt és min?
|
||||||
changes = Column(JSON, nullable=True)
|
action = Column(String(100), nullable=False, index=True)
|
||||||
timestamp = Column(DateTime(timezone=True), server_default=func.now())
|
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")
|
user = relationship("User")
|
||||||
@@ -7,12 +7,12 @@ from sqlalchemy.sql import func
|
|||||||
from app.db.base_class import Base
|
from app.db.base_class import Base
|
||||||
|
|
||||||
class UserRole(str, enum.Enum):
|
class UserRole(str, enum.Enum):
|
||||||
|
superadmin = "superadmin"
|
||||||
admin = "admin"
|
admin = "admin"
|
||||||
user = "user"
|
user = "user"
|
||||||
service = "service"
|
service = "service"
|
||||||
fleet_manager = "fleet_manager"
|
fleet_manager = "fleet_manager"
|
||||||
driver = "driver"
|
driver = "driver"
|
||||||
superadmin = "superadmin" # Hozzáadva a biztonság kedvéért
|
|
||||||
|
|
||||||
class Person(Base):
|
class Person(Base):
|
||||||
__tablename__ = "persons"
|
__tablename__ = "persons"
|
||||||
@@ -24,16 +24,9 @@ class Person(Base):
|
|||||||
|
|
||||||
last_name = Column(String, nullable=False)
|
last_name = Column(String, nullable=False)
|
||||||
first_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)
|
phone = Column(String, nullable=True)
|
||||||
|
|
||||||
identity_docs = Column(JSON, server_default=text("'{}'::jsonb"))
|
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)
|
is_active = Column(Boolean, default=False, nullable=False)
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
@@ -49,27 +42,27 @@ class User(Base):
|
|||||||
hashed_password = Column(String, nullable=True)
|
hashed_password = Column(String, nullable=True)
|
||||||
role = Column(Enum(UserRole), default=UserRole.user)
|
role = Column(Enum(UserRole), default=UserRole.user)
|
||||||
is_active = Column(Boolean, default=False)
|
is_active = Column(Boolean, default=False)
|
||||||
region_code = Column(String, default="HU")
|
|
||||||
is_deleted = Column(Boolean, default=False)
|
is_deleted = Column(Boolean, default=False)
|
||||||
person_id = Column(BigInteger, ForeignKey("data.persons.id"), nullable=True)
|
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_level = Column(String(30), server_default="individual")
|
||||||
scope_id = Column(String(50))
|
scope_id = Column(String(50))
|
||||||
custom_permissions = Column(JSON, server_default=text("'{}'::jsonb"))
|
custom_permissions = Column(JSON, server_default=text("'{}'::jsonb"))
|
||||||
|
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
# Kapcsolatok
|
|
||||||
person = relationship("Person", back_populates="users")
|
person = relationship("Person", back_populates="users")
|
||||||
wallet = relationship("Wallet", back_populates="user", uselist=False)
|
wallet = relationship("Wallet", back_populates="user", uselist=False)
|
||||||
stats = relationship("UserStats", back_populates="user", uselist=False)
|
stats = relationship("UserStats", back_populates="user", uselist=False)
|
||||||
ownership_history = relationship("VehicleOwnership", back_populates="user")
|
ownership_history = relationship("VehicleOwnership", back_populates="user")
|
||||||
owned_organizations = relationship("Organization", back_populates="owner")
|
owned_organizations = relationship("Organization", back_populates="owner")
|
||||||
|
|
||||||
|
# A Wallet és VerificationToken osztályok maradnak változatlanok...
|
||||||
class Wallet(Base):
|
class Wallet(Base):
|
||||||
__tablename__ = "wallets"
|
__tablename__ = "wallets"
|
||||||
__table_args__ = {"schema": "data"}
|
__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 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):
|
class Translation(Base):
|
||||||
__tablename__ = "translations"
|
__tablename__ = "translations"
|
||||||
@@ -12,4 +13,4 @@ class Translation(Base):
|
|||||||
key = Column(String(100), nullable=False, index=True)
|
key = Column(String(100), nullable=False, index=True)
|
||||||
lang_code = Column(String(5), nullable=False, index=True)
|
lang_code = Column(String(5), nullable=False, index=True)
|
||||||
value = Column(Text, nullable=False)
|
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 os
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
import json
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -18,18 +19,15 @@ from app.services.email_manager import email_manager
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.services.config_service import config
|
from app.services.config_service import config
|
||||||
from app.services.geo_service import GeoService
|
from app.services.geo_service import GeoService
|
||||||
|
from app.services.security_service import security_service # Sentinel integráció
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class AuthService:
|
class AuthService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
|
async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
|
||||||
"""
|
"""Step 1: Lite Regisztráció."""
|
||||||
Step 1: Lite Regisztráció (Master Book 1.1)
|
|
||||||
Új User és ideiglenes Person rekord létrehozása nyelvi és időzóna adatokkal.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# Ideiglenes Person rekord a KYC-ig
|
|
||||||
new_person = Person(
|
new_person = Person(
|
||||||
first_name=user_in.first_name,
|
first_name=user_in.first_name,
|
||||||
last_name=user_in.last_name,
|
last_name=user_in.last_name,
|
||||||
@@ -46,14 +44,12 @@ class AuthService:
|
|||||||
is_active=False,
|
is_active=False,
|
||||||
is_deleted=False,
|
is_deleted=False,
|
||||||
region_code=user_in.region_code,
|
region_code=user_in.region_code,
|
||||||
# --- NYELVI ÉS ADMIN BEÁLLÍTÁSOK MENTÉSE ---
|
|
||||||
preferred_language=user_in.lang,
|
preferred_language=user_in.lang,
|
||||||
timezone=user_in.timezone
|
timezone=user_in.timezone
|
||||||
)
|
)
|
||||||
db.add(new_user)
|
db.add(new_user)
|
||||||
await db.flush()
|
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)
|
reg_hours = await config.get_setting("auth_registration_hours", region_code=user_in.region_code, default=48)
|
||||||
token_val = uuid.uuid4()
|
token_val = uuid.uuid4()
|
||||||
db.add(VerificationToken(
|
db.add(VerificationToken(
|
||||||
@@ -63,14 +59,12 @@ class AuthService:
|
|||||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reg_hours))
|
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}"
|
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
|
||||||
await email_manager.send_email(
|
await email_manager.send_email(
|
||||||
recipient=user_in.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},
|
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()
|
await db.commit()
|
||||||
@@ -83,23 +77,16 @@ class AuthService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def complete_kyc(db: AsyncSession, user_id: int, kyc_in: UserKYCComplete):
|
async def complete_kyc(db: AsyncSession, user_id: int, kyc_in: UserKYCComplete):
|
||||||
"""
|
"""1.3. Fázis: Atomi Tranzakció & Shadow Identity."""
|
||||||
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.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 1. Aktuális technikai User lekérése
|
|
||||||
stmt = select(User).options(joinedload(User.person)).where(User.id == user_id)
|
stmt = select(User).options(joinedload(User.person)).where(User.id == user_id)
|
||||||
res = await db.execute(stmt)
|
res = await db.execute(stmt)
|
||||||
user = res.scalar_one_or_none()
|
user = res.scalar_one_or_none()
|
||||||
if not user: return None
|
if not user: return None
|
||||||
|
|
||||||
# --- PÉNZNEM PREFERENCIA FRISSÍTÉSE ---
|
|
||||||
if hasattr(kyc_in, 'preferred_currency') and kyc_in.preferred_currency:
|
if hasattr(kyc_in, 'preferred_currency') and kyc_in.preferred_currency:
|
||||||
user.preferred_currency = kyc_in.preferred_currency
|
user.preferred_currency = kyc_in.preferred_currency
|
||||||
|
|
||||||
# 2. Shadow Identity Ellenőrzése
|
|
||||||
identity_stmt = select(Person).where(and_(
|
identity_stmt = select(Person).where(and_(
|
||||||
Person.mothers_last_name == kyc_in.mothers_last_name,
|
Person.mothers_last_name == kyc_in.mothers_last_name,
|
||||||
Person.mothers_first_name == kyc_in.mothers_first_name,
|
Person.mothers_first_name == kyc_in.mothers_first_name,
|
||||||
@@ -115,7 +102,6 @@ class AuthService:
|
|||||||
else:
|
else:
|
||||||
active_person = user.person
|
active_person = user.person
|
||||||
|
|
||||||
# 3. Címkezelés
|
|
||||||
addr_id = await GeoService.get_or_create_full_address(
|
addr_id = await GeoService.get_or_create_full_address(
|
||||||
db,
|
db,
|
||||||
zip_code=kyc_in.address_zip,
|
zip_code=kyc_in.address_zip,
|
||||||
@@ -126,7 +112,6 @@ class AuthService:
|
|||||||
parcel_id=kyc_in.address_hrsz
|
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_last_name = kyc_in.mothers_last_name
|
||||||
active_person.mothers_first_name = kyc_in.mothers_first_name
|
active_person.mothers_first_name = kyc_in.mothers_first_name
|
||||||
active_person.birth_place = kyc_in.birth_place
|
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.ice_contact = jsonable_encoder(kyc_in.ice_contact)
|
||||||
active_person.is_active = True
|
active_person.is_active = True
|
||||||
|
|
||||||
# 5. Új, izolált INDIVIDUAL szervezet (4.2.3) i18n beállításokkal
|
|
||||||
new_org = Organization(
|
new_org = Organization(
|
||||||
full_name=f"{active_person.last_name} {active_person.first_name} Egyéni Flotta",
|
full_name=f"{active_person.last_name} {active_person.first_name} Egyéni Flotta",
|
||||||
name=f"{active_person.last_name} Flotta",
|
name=f"{active_person.last_name} Flotta",
|
||||||
@@ -146,7 +130,6 @@ class AuthService:
|
|||||||
is_transferable=False,
|
is_transferable=False,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
status="verified",
|
status="verified",
|
||||||
# Megörökölt adminisztrációs adatok
|
|
||||||
language=user.preferred_language,
|
language=user.preferred_language,
|
||||||
default_currency=user.preferred_currency,
|
default_currency=user.preferred_currency,
|
||||||
country_code=user.region_code
|
country_code=user.region_code
|
||||||
@@ -154,7 +137,6 @@ class AuthService:
|
|||||||
db.add(new_org)
|
db.add(new_org)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
# 6. Tagság és Jogosultságok
|
|
||||||
db.add(OrganizationMember(
|
db.add(OrganizationMember(
|
||||||
organization_id=new_org.id,
|
organization_id=new_org.id,
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@@ -162,7 +144,6 @@ class AuthService:
|
|||||||
permissions={"can_add_asset": True, "can_view_costs": True, "is_admin": True}
|
permissions={"can_add_asset": True, "can_view_costs": True, "is_admin": True}
|
||||||
))
|
))
|
||||||
|
|
||||||
# 7. Wallet & Stats
|
|
||||||
db.add(Wallet(
|
db.add(Wallet(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
coin_balance=0,
|
coin_balance=0,
|
||||||
@@ -171,7 +152,6 @@ class AuthService:
|
|||||||
))
|
))
|
||||||
db.add(UserStats(user_id=user.id, total_xp=0, current_level=1))
|
db.add(UserStats(user_id=user.id, total_xp=0, current_level=1))
|
||||||
|
|
||||||
# 8. Aktiválás
|
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
@@ -182,6 +162,39 @@ class AuthService:
|
|||||||
logger.error(f"KYC Atomi Tranzakció Hiba: {str(e)}")
|
logger.error(f"KYC Atomi Tranzakció Hiba: {str(e)}")
|
||||||
raise 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
|
@staticmethod
|
||||||
async def verify_email(db: AsyncSession, token_str: str):
|
async def verify_email(db: AsyncSession, token_str: str):
|
||||||
try:
|
try:
|
||||||
@@ -227,13 +240,12 @@ class AuthService:
|
|||||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reset_hours))
|
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}"
|
reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}"
|
||||||
await email_manager.send_email(
|
await email_manager.send_email(
|
||||||
recipient=email,
|
recipient=email,
|
||||||
template_key="pwd_reset", # hu.json: email.pwd_reset_subject stb.
|
template_key="pwd_reset",
|
||||||
variables={"link": reset_link},
|
variables={"link": reset_link},
|
||||||
lang=user.preferred_language # Adatbázisból kinyert nyelv
|
lang=user.preferred_language
|
||||||
)
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
return "success"
|
return "success"
|
||||||
|
|||||||
@@ -1,47 +1,106 @@
|
|||||||
|
import logging
|
||||||
|
import math
|
||||||
|
from decimal import Decimal
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from app.models.gamification import UserStats, PointsLedger
|
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:
|
class GamificationService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def process_activity(db: AsyncSession, user_id: int, xp_amount: int, social_amount: int, reason: str):
|
async def get_config(db: AsyncSession):
|
||||||
"""
|
"""Kiolvassa a GAMIFICATION_MASTER_CONFIG-ot a rendszerparaméterekből."""
|
||||||
XP növelés, Szintlépés csekk és Automata Kredit váltás.
|
stmt = select(SystemParameter).where(SystemParameter.key == "GAMIFICATION_MASTER_CONFIG")
|
||||||
"""
|
res = await db.execute(stmt)
|
||||||
# 1. User statisztika lekérése
|
param = res.scalar_one_or_none()
|
||||||
stmt = select(UserStats).where(UserStats.user_id == user_id)
|
return param.value if param else {
|
||||||
stats = (await db.execute(stmt)).scalar_one_or_none()
|
"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:
|
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)
|
db.add(stats)
|
||||||
|
|
||||||
# 2. Részletes Logolás (PointsLedger) - A visszakövethetőség miatt
|
# 3. Büntető logika (Penalty)
|
||||||
db.add(PointsLedger(
|
if is_penalty:
|
||||||
user_id=user_id,
|
stats.penalty_points += xp_amount
|
||||||
xp_gain=xp_amount,
|
th = config["penalty_logic"]["thresholds"]
|
||||||
social_gain=social_amount,
|
if stats.penalty_points >= th["level_3"]: stats.restriction_level = 3
|
||||||
reason=reason
|
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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Külön log a váltásról
|
db.add(PointsLedger(user_id=user_id, points=0, penalty_change=xp_amount, reason=f"PENALTY: {reason}"))
|
||||||
db.add(PointsLedger(user_id=user_id, reason=f"Auto-conversion: {new_credits} Credits", credits_change=new_credits))
|
await db.commit()
|
||||||
|
return stats
|
||||||
|
|
||||||
|
# 4. Dinamikus szorzó alkalmazása
|
||||||
|
multipliers = config["penalty_logic"]["multipliers"]
|
||||||
|
multiplier = multipliers.get(f"level_{stats.restriction_level}", 1.0)
|
||||||
|
|
||||||
|
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()
|
await db.commit()
|
||||||
return stats
|
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.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
from app.models.translation import Translation
|
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:
|
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]] = {}
|
_published_cache: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def load_cache(cls, db: AsyncSession):
|
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(
|
result = await db.execute(
|
||||||
select(Translation).where(Translation.is_published == True)
|
select(Translation).where(Translation.is_published == True)
|
||||||
)
|
)
|
||||||
@@ -20,27 +26,80 @@ class TranslationService:
|
|||||||
if t.lang_code not in cls._published_cache:
|
if t.lang_code not in cls._published_cache:
|
||||||
cls._published_cache[t.lang_code] = {}
|
cls._published_cache[t.lang_code] = {}
|
||||||
cls._published_cache[t.lang_code][t.key] = t.value
|
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
|
@classmethod
|
||||||
def get_text(cls, key: str, lang: str = "en") -> str:
|
def get_text(cls, key: str, lang: str = "hu", variables: Optional[Dict[str, Any]] = None) -> str:
|
||||||
"""Villámgyors lekérés a memóriából Fallback logikával."""
|
"""
|
||||||
# 1. Kért nyelv
|
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)
|
text = cls._published_cache.get(lang, {}).get(key)
|
||||||
if text: return text
|
|
||||||
|
|
||||||
# 2. Fallback: Angol
|
|
||||||
if lang != "en":
|
|
||||||
text = cls._published_cache.get("en", {}).get(key)
|
|
||||||
if text: return text
|
|
||||||
|
|
||||||
return f"[{key}]"
|
# 2. Fallback angolra, ha nincs meg a kért nyelven
|
||||||
|
if not text and lang != "en":
|
||||||
|
text = cls._published_cache.get("en", {}).get(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
|
@classmethod
|
||||||
async def publish_all(cls, db: AsyncSession):
|
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(
|
await db.execute(
|
||||||
update(Translation).where(Translation.is_published == False).values(is_published=True)
|
update(Translation).where(Translation.is_published == False).values(is_published=True)
|
||||||
)
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await cls.load_cache(db)
|
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!"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,4 +101,47 @@ A rendszer különválasztja a tekintélyt és a jutalmat:
|
|||||||
Minden érték (szorzók, határok) a \`GAMIFICATION_MASTER_CONFIG\` JSON paraméterben állítható Admin felületről, kódmódosítás nélkül.
|
Minden érték (szorzók, határok) a \`GAMIFICATION_MASTER_CONFIG\` JSON paraméterben állítható Admin felületről, kódmódosítás nélkül.
|
||||||
|
|
||||||
### 3. Audit
|
### 3. Audit
|
||||||
Minden pontmozgás a \`PointsLedger\` táblába kerül rögzítésre a visszakövethetőség érdekében.
|
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.
|
||||||
@@ -249,4 +249,41 @@ A rendszer most már képes egyetlen KYC folyamat alatt aktiválni a felhasznál
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- `AssetCost` modell mezőnevek szinkronizálva a pénzügyi standardokhoz (`amount_local`, `amount_eur`).
|
- `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.
|
- `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.
|
||||||
@@ -102,4 +102,20 @@ A rendszer a \`system_parameters\` táblában tárolt \`RBAC_MASTER_CONFIG\` JSO
|
|||||||
### 2. Scope (Hatókör) Védelem
|
### 2. Scope (Hatókör) Védelem
|
||||||
Minden műveletnél ellenőrizzük a \`scope_id\` egyezését:
|
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.
|
- 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.
|
- 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