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

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

View File

@@ -1,6 +1,7 @@
import os
import logging
import uuid
import json
from datetime import datetime, timedelta, timezone
from typing import Optional
@@ -18,18 +19,15 @@ from app.services.email_manager import email_manager
from app.core.config import settings
from app.services.config_service import config
from app.services.geo_service import GeoService
from app.services.security_service import security_service # Sentinel integráció
logger = logging.getLogger(__name__)
class AuthService:
@staticmethod
async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
"""
Step 1: Lite Regisztráció (Master Book 1.1)
Új User és ideiglenes Person rekord létrehozása nyelvi és időzóna adatokkal.
"""
"""Step 1: Lite Regisztráció."""
try:
# Ideiglenes Person rekord a KYC-ig
new_person = Person(
first_name=user_in.first_name,
last_name=user_in.last_name,
@@ -46,14 +44,12 @@ class AuthService:
is_active=False,
is_deleted=False,
region_code=user_in.region_code,
# --- NYELVI ÉS ADMIN BEÁLLÍTÁSOK MENTÉSE ---
preferred_language=user_in.lang,
timezone=user_in.timezone
)
db.add(new_user)
await db.flush()
# Regisztrációs token generálása
reg_hours = await config.get_setting("auth_registration_hours", region_code=user_in.region_code, default=48)
token_val = uuid.uuid4()
db.add(VerificationToken(
@@ -63,14 +59,12 @@ class AuthService:
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reg_hours))
))
# --- EMAIL KÜLDÉSE A VÁLASZTOTT NYELVEN ---
# Master Book 3.2: Nincs manuális subject, a nyelvi kulcs alapján töltődik be
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
await email_manager.send_email(
recipient=user_in.email,
template_key="reg", # hu.json: email.reg_subject, reg_greeting stb.
template_key="reg",
variables={"first_name": user_in.first_name, "link": verification_link},
lang=user_in.lang # Dinamikus nyelvválasztás
lang=user_in.lang
)
await db.commit()
@@ -83,23 +77,16 @@ class AuthService:
@staticmethod
async def complete_kyc(db: AsyncSession, user_id: int, kyc_in: UserKYCComplete):
"""
1.3. Fázis: Atomi Tranzakció & Shadow Identity
Felismeri a visszatérő Person-t, de új User-ként, izolált flottával indít.
Frissíti a nyelvi és pénzügyi beállításokat.
"""
"""1.3. Fázis: Atomi Tranzakció & Shadow Identity."""
try:
# 1. Aktuális technikai User lekérése
stmt = select(User).options(joinedload(User.person)).where(User.id == user_id)
res = await db.execute(stmt)
user = res.scalar_one_or_none()
if not user: return None
# --- PÉNZNEM PREFERENCIA FRISSÍTÉSE ---
if hasattr(kyc_in, 'preferred_currency') and kyc_in.preferred_currency:
user.preferred_currency = kyc_in.preferred_currency
# 2. Shadow Identity Ellenőrzése
identity_stmt = select(Person).where(and_(
Person.mothers_last_name == kyc_in.mothers_last_name,
Person.mothers_first_name == kyc_in.mothers_first_name,
@@ -115,7 +102,6 @@ class AuthService:
else:
active_person = user.person
# 3. Címkezelés
addr_id = await GeoService.get_or_create_full_address(
db,
zip_code=kyc_in.address_zip,
@@ -126,7 +112,6 @@ class AuthService:
parcel_id=kyc_in.address_hrsz
)
# 4. Person adatok frissítése
active_person.mothers_last_name = kyc_in.mothers_last_name
active_person.mothers_first_name = kyc_in.mothers_first_name
active_person.birth_place = kyc_in.birth_place
@@ -137,7 +122,6 @@ class AuthService:
active_person.ice_contact = jsonable_encoder(kyc_in.ice_contact)
active_person.is_active = True
# 5. Új, izolált INDIVIDUAL szervezet (4.2.3) i18n beállításokkal
new_org = Organization(
full_name=f"{active_person.last_name} {active_person.first_name} Egyéni Flotta",
name=f"{active_person.last_name} Flotta",
@@ -146,7 +130,6 @@ class AuthService:
is_transferable=False,
is_active=True,
status="verified",
# Megörökölt adminisztrációs adatok
language=user.preferred_language,
default_currency=user.preferred_currency,
country_code=user.region_code
@@ -154,7 +137,6 @@ class AuthService:
db.add(new_org)
await db.flush()
# 6. Tagság és Jogosultságok
db.add(OrganizationMember(
organization_id=new_org.id,
user_id=user.id,
@@ -162,7 +144,6 @@ class AuthService:
permissions={"can_add_asset": True, "can_view_costs": True, "is_admin": True}
))
# 7. Wallet & Stats
db.add(Wallet(
user_id=user.id,
coin_balance=0,
@@ -171,7 +152,6 @@ class AuthService:
))
db.add(UserStats(user_id=user.id, total_xp=0, current_level=1))
# 8. Aktiválás
user.is_active = True
await db.commit()
@@ -182,6 +162,39 @@ class AuthService:
logger.error(f"KYC Atomi Tranzakció Hiba: {str(e)}")
raise e
@staticmethod
async def soft_delete_user(db: AsyncSession, user_id: int, reason: str, actor_id: int):
"""
Step 2 utáni Soft-Delete: Email felszabadítás és izoláció.
Az email átnevezésre kerül, így az eredeti cím újra regisztrálható 'tiszta lappal'.
"""
stmt = select(User).where(User.id == user_id)
user = (await db.execute(stmt)).scalar_one_or_none()
if not user or user.is_deleted:
return False
old_email = user.email
# Email felszabadítása: deleted_ID_TIMESTAMP_EMAIL formátumban
user.email = f"deleted_{user.id}_{datetime.now().strftime('%Y%m%d')}_{old_email}"
user.is_deleted = True
user.is_active = False
# Sentinel AuditLog bejegyzés
await security_service.log_event(
db,
user_id=actor_id,
action="USER_SOFT_DELETE",
severity="warning",
target_type="User",
target_id=str(user_id),
old_data={"email": old_email},
new_data={"is_deleted": True, "reason": reason}
)
await db.commit()
return True
@staticmethod
async def verify_email(db: AsyncSession, token_str: str):
try:
@@ -227,13 +240,12 @@ class AuthService:
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reset_hours))
))
# --- EMAIL KÜLDÉSE A FELHASZNÁLÓ SAJÁT NYELVÉN ---
reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}"
await email_manager.send_email(
recipient=email,
template_key="pwd_reset", # hu.json: email.pwd_reset_subject stb.
template_key="pwd_reset",
variables={"link": reset_link},
lang=user.preferred_language # Adatbázisból kinyert nyelv
lang=user.preferred_language
)
await db.commit()
return "success"