2026.06.05 frontend javítgatás és új belépési logika megvalósítva

This commit is contained in:
Roo
2026-06-05 05:46:04 +00:00
parent 59a30ac428
commit 18524a08f2
38 changed files with 3967 additions and 1028 deletions

View File

@@ -68,7 +68,16 @@ async def get_current_user(
# JAVÍTVA: Modern SQLAlchemy 2.0 aszinkron lekérdezés with eager loading
# We need to load the person relationship to avoid lazy loading issues
stmt = select(User).where(User.id == int(user_id)).options(joinedload(User.person))
# Also load Person.address for nested profile response
from app.models.identity import Person
from app.models.identity.address import Address
stmt = (
select(User)
.where(User.id == int(user_id))
.options(
joinedload(User.person).joinedload(Person.address)
)
)
result = await db.execute(stmt)
user = result.unique().scalar_one_or_none()

View File

@@ -1,4 +1,5 @@
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/auth.py
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Request, Form, Response
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
@@ -10,9 +11,10 @@ from app.core.config import settings
from app.services.config_service import config
from app.schemas.auth import UserLiteRegister, Token, UserKYCComplete
from app.api.deps import get_current_user
from app.models.identity import User # JAVÍTVA: Új központi modell
from app.models.identity import User, Device, UserDeviceLink # JAVÍTVA: Device-Hub modellek
from app.core.translation_helper import t # Translation helper
from pydantic import BaseModel, Field, EmailStr
from datetime import datetime, timezone
router = APIRouter()
@@ -34,18 +36,71 @@ async def login(
response: Response,
db: AsyncSession = Depends(get_db),
form_data: OAuth2PasswordRequestForm = Depends(),
remember_me: bool = Form(False)
remember_me: bool = Form(False),
device_fingerprint: Optional[str] = Form(None)
):
"""
Bejelentkezés remember_me opcióval.
Bejelentkezés remember_me opcióval és Device Fingerprinting támogatással.
A remember_me paramétert explicit Form mezőként kell elküldeni, mivel az
OAuth2PasswordRequestForm nem támogatja alapból.
A device_fingerprint opcionális, a frontend FingerprintJS által generált hash.
"""
user = await AuthService.authenticate(db, form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=401, detail=t("AUTH.INVALID_CREDENTIALS"))
# DEBUG: Inaktív fiók explicit ellenőrzése
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="AUTH.USER_INACTIVE"
)
# ── Device Fingerprint Tracking ──
if device_fingerprint:
# 1. Ellenőrizzük, létezik-e már a Device a hash alapján
existing_device = await db.execute(
select(Device).where(Device.fingerprint_hash == device_fingerprint)
)
device = existing_device.scalar_one_or_none()
if not device:
# Új eszköz létrehozása
device = Device(
fingerprint_hash=device_fingerprint,
risk_score=100,
is_banned=False
)
db.add(device)
# 2. Ellenőrizzük a UserDeviceLink-et
existing_link = await db.execute(
select(UserDeviceLink).where(
UserDeviceLink.user_id == user.id,
UserDeviceLink.device_hash == device_fingerprint
)
)
link = existing_link.scalar_one_or_none()
now = datetime.now(timezone.utc)
if link:
# Létező kapcsolat frissítése
link.last_seen_at = now
link.login_count = link.login_count + 1
else:
# Új kapcsolat létrehozása
link = UserDeviceLink(
user_id=user.id,
device_hash=device_fingerprint,
first_seen_at=now,
last_seen_at=now,
login_count=1
)
db.add(link)
await db.commit()
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default=DEFAULT_RANK_MAP)
role_name = user.role.value if hasattr(user.role, 'value') else str(user.role)
role_key = role_name.upper() # A DEFAULT_RANK_MAP nagybetűs kulcsokat vár
@@ -82,16 +137,112 @@ async def login(
class VerifyEmailRequest(BaseModel):
token: str = Field(..., description="Email verification token (UUID)")
device_fingerprint: Optional[str] = Field(None, description="Device fingerprint hash from FingerprintJS")
@router.post("/verify-email")
async def verify_email(request: VerifyEmailRequest, db: AsyncSession = Depends(get_db)):
@router.post("/verify-email", response_model=Token)
async def verify_email(
request: VerifyEmailRequest,
response: Response,
db: AsyncSession = Depends(get_db)
):
"""
Email megerősítés token alapján.
Email megerősítés token alapján + automatikus bejelentkezés (Magic Link).
Sikeres verifikáció után azonnal JWT tokent ad vissza, így a felhasználó
bejelentkezik anélkül, hogy külön be kellene írnia a jelszavát.
"""
success = await AuthService.verify_email(db, request.token)
if not success:
user = await AuthService.verify_email(db, request.token)
if not user:
raise HTTPException(status_code=400, detail=t("AUTH.INVALID_OR_EXPIRED_TOKEN"))
return {"status": "success", "message": t("AUTH.EMAIL_VERIFICATION_SUCCESS")}
# ── Device Fingerprint Tracking (Magic Link) ──
if request.device_fingerprint:
# 1. Ellenőrizzük, létezik-e már a Device a hash alapján
existing_device = await db.execute(
select(Device).where(Device.fingerprint_hash == request.device_fingerprint)
)
device = existing_device.scalar_one_or_none()
if not device:
# Új eszköz létrehozása
device = Device(
fingerprint_hash=request.device_fingerprint,
risk_score=100,
is_banned=False
)
db.add(device)
# 2. Ellenőrizzük a UserDeviceLink-et
existing_link = await db.execute(
select(UserDeviceLink).where(
UserDeviceLink.user_id == user.id,
UserDeviceLink.device_hash == request.device_fingerprint
)
)
link = existing_link.scalar_one_or_none()
now = datetime.now(timezone.utc)
if link:
# Létező kapcsolat frissítése
link.last_seen_at = now
link.login_count = link.login_count + 1
else:
# Új kapcsolat létrehozása
link = UserDeviceLink(
user_id=user.id,
device_hash=request.device_fingerprint,
first_seen_at=now,
last_seen_at=now,
login_count=1
)
db.add(link)
await db.commit()
# Generálj JWT tokent a most aktivált felhasználóhoz (ugyanaz a logika, mint login-nál)
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default=DEFAULT_RANK_MAP)
role_name = user.role.value if hasattr(user.role, 'value') else str(user.role)
role_key = role_name.upper()
token_data = {
"sub": str(user.id),
"role": role_name,
"rank": ranks.get(role_key, 10),
"scope_level": user.scope_level or "individual",
"scope_id": str(user.scope_id) if user.scope_id else str(user.id)
}
# Magic Link esetén ne használjunk remember_me-t (alapértelmezett 1 napos token)
access, refresh = create_tokens(data=token_data, remember_me=False)
# Refresh token lejárat
max_age_days = await config.get_setting(db, "auth_refresh_default_days", default=1)
max_age_sec = int(max_age_days) * 24 * 60 * 60
response.set_cookie(
key="refresh_token",
value=refresh,
httponly=True,
secure=True,
samesite="lax",
max_age=max_age_sec
)
return {"access_token": access, "refresh_token": refresh, "token_type": "bearer", "is_active": user.is_active}
class ResendVerificationRequest(BaseModel):
email: EmailStr = Field(..., description="Email cím az aktiváló levél újraküldéséhez")
@router.post("/resend-verification")
async def resend_verification(request: ResendVerificationRequest, db: AsyncSession = Depends(get_db)):
"""
Aktiváló e-mail újraküldése.
Mindig sikeres választ ad, hogy megelőzzük az email enumerációt.
"""
result = await AuthService.resend_verification(db, request.email)
return {
"status": "success",
"message": "Ha ez az email cím regisztrálva van és még nem aktív, akkor elküldtük az aktiváló e-mailt."
}
class ForgotPasswordRequest(BaseModel):
email: EmailStr = Field(..., description="Email cím a jelszó visszaállításhoz")
@@ -100,10 +251,18 @@ class ForgotPasswordRequest(BaseModel):
async def forgot_password(request: ForgotPasswordRequest, db: AsyncSession = Depends(get_db)):
"""
Elfelejtett jelszó folyamat indítása.
Mindig sikeres választ ad, hogy megelőzzük az email enumerációt.
DEBUG MODE: Explicit hibákat dob az email küldési problémák diagnosztizálásához.
"""
result = await AuthService.initiate_password_reset(db, request.email)
# Mindig ugyanazt a választ adjuk, függetlenül attól, hogy létezik-e a felhasználó
# DEBUG: Ha nem található a user, dobjunk explicit hibát
if result == "not_found":
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="A felhasználó nem található ezzel az email címmel."
)
# Ha success, adjuk vissza a sikeres választ
return {
"status": "success",
"message": "Ha ez az email cím regisztrálva van, akkor elküldtünk egy jelszó-visszaállítási linket."

View File

@@ -1,8 +1,9 @@
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/system_parameters.py
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
from typing import List, Optional
from sqlalchemy import select, update, and_
from typing import List, Optional, Dict, Any
from app.api.deps import get_db, get_current_user
from app.schemas.system import (
@@ -11,9 +12,11 @@ from app.schemas.system import (
SystemParameterCreate,
)
from app.models.system import SystemParameter, ParameterScope
from app.models.identity.address import GeoPostalCode
from app.models.identity import UserRole
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/", response_model=List[SystemParameterResponse])
@@ -41,6 +44,40 @@ async def list_system_parameters(
return parameters
@router.get("/zip-lookup", response_model=Dict[str, Any])
async def zip_lookup(
country_code: str = Query("HU", description="Országkód (pl. HU, AT, DE)"),
zip_code: str = Query(..., min_length=1, description="Irányítószám"),
db: AsyncSession = Depends(get_db),
):
"""
Belső irányítószám lekérdező végpont.
A system.geo_postal_codes táblában keres a megadott country_code és zip_code párosra.
Ha megtalálja, visszaadja a várost: {"city": "Budapest"}.
Ha nem találja, 404-es hibát dob.
"""
if not zip_code.strip():
raise HTTPException(status_code=400, detail="zip_code is required")
stmt = select(GeoPostalCode).where(
and_(
GeoPostalCode.country_code == country_code,
GeoPostalCode.zip_code == zip_code.strip(),
)
).limit(1)
result = await db.execute(stmt)
postal_code = result.scalar_one_or_none()
if not postal_code:
raise HTTPException(
status_code=404,
detail=f"No city found for country_code='{country_code}' and zip_code='{zip_code}'"
)
return {"city": postal_code.city}
@router.get("/{key}", response_model=SystemParameterResponse)
async def get_system_parameter(
key: str,
@@ -128,5 +165,4 @@ async def update_system_parameter(
await db.execute(stmt)
await db.commit()
await db.refresh(parameter)
return parameter

View File

@@ -1,16 +1,23 @@
#/opt/docker/dev/service_finder/backend/app/api/v1/endpoints/users.py
import copy
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import select, or_
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.attributes import flag_modified
from typing import Dict, Any, List, Optional
from datetime import datetime
from app.api.deps import get_db, get_current_user
from app.schemas.user import UserResponse, UserUpdate, ActiveOrganizationUpdate, UserWithTokenResponse
from app.models.identity import User
from app.schemas.user import UserResponse, UserUpdate, ActiveOrganizationUpdate, UserWithTokenResponse, PersonUpdate, ChangePasswordRequest
from app.models.identity import User, Person
from app.models.identity.address import Address
from app.services.trust_engine import TrustEngine
from app.core.security import create_tokens, DEFAULT_RANK_MAP
from app.services.geo_service import GeoService
from app.services.auth_service import AuthService
from app.core.security import create_tokens, DEFAULT_RANK_MAP, verify_password, get_password_hash
from app.core.config import settings
from pydantic import BaseModel
@@ -33,26 +40,105 @@ class NetworkResponse(BaseModel):
level2: List[NetworkMemberL2L3]
level3: List[NetworkMemberL2L3]
def _build_user_response(user: User, active_org_id: Optional[int] = None) -> dict:
"""
Segédfüggvény a UserResponse dict előállításához.
Beágyazza a Person és Address adatokat a 'person' mezőbe.
"""
from app.models.marketplace.organization import Organization, OrganizationMember
from app.models.marketplace.organization import OrgUserRole
# Determine active organization ID
if active_org_id is None:
if user.scope_id is not None:
try:
active_org_id = int(user.scope_id)
except (ValueError, TypeError):
active_org_id = None
if active_org_id is None:
# 1. Check if user is a member of any organization with ADMIN/OWNER role
# (This is a helper - in real usage the caller should pass active_org_id)
pass
person = user.person
person_data = None
if person:
address_data = None
if person.address:
address_data = {
"zip": getattr(person.address, 'zip', None) or getattr(getattr(person.address, 'postal_code', None), 'zip_code', None),
"city": getattr(person.address, 'city', None) or getattr(getattr(person.address, 'postal_code', None), 'city', None),
"street_name": person.address.street_name,
"street_type": person.address.street_type,
"house_number": person.address.house_number,
"stairwell": person.address.stairwell,
"floor": person.address.floor,
"door": person.address.door,
"parcel_id": person.address.parcel_id,
"full_address_text": person.address.full_address_text,
"latitude": person.address.latitude,
"longitude": person.address.longitude,
}
person_data = {
"id": person.id,
"id_uuid": str(person.id_uuid) if person.id_uuid else None,
"first_name": person.first_name,
"last_name": person.last_name,
"phone": person.phone,
"mothers_last_name": person.mothers_last_name,
"mothers_first_name": person.mothers_first_name,
"birth_place": person.birth_place,
"birth_date": person.birth_date,
"identity_docs": person.identity_docs,
"ice_contact": person.ice_contact,
"is_active": person.is_active,
"address": address_data,
}
# Get first_name/last_name from person for backward compatibility
first_name = person.first_name if person else ""
last_name = person.last_name if person else ""
return {
"id": user.id,
"email": user.email,
"first_name": first_name,
"last_name": last_name,
"is_active": user.is_active,
"region_code": user.region_code,
"person_id": user.person_id,
"role": user.role.value if hasattr(user.role, 'value') else str(user.role),
"subscription_plan": user.subscription_plan,
"scope_level": user.scope_level or "individual",
"scope_id": str(active_org_id) if active_org_id else None,
"ui_mode": user.ui_mode or "personal",
"active_organization_id": active_org_id,
"preferred_language": user.preferred_language or "hu",
"person": person_data,
}
@router.get("/me", response_model=UserResponse)
async def read_users_me(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Visszaadja a bejelentkezett felhasználó profilját"""
from sqlalchemy import select, or_
"""Visszaadja a bejelentkezett felhasználó profilját beágyazott Person és Address adatokkal."""
from app.models.marketplace.organization import Organization, OrganizationMember
from app.models.marketplace.organization import OrgUserRole
# Determine active organization ID
active_org_id = None
# If user already has a scope_id, use it
if current_user.scope_id is not None:
try:
active_org_id = int(current_user.scope_id)
except (ValueError, TypeError):
active_org_id = None
# If still no active org ID, try to find user's primary organization
if active_org_id is None:
# 1. Check if user is a member of any organization with ADMIN/OWNER role
@@ -63,10 +149,10 @@ async def read_users_me(
OrganizationMember.role == OrgUserRole.OWNER
)
).limit(1)
result = await db.execute(stmt)
org_member_row = result.first()
if org_member_row:
active_org_id = org_member_row[0]
else:
@@ -76,7 +162,7 @@ async def read_users_me(
).limit(1)
result = await db.execute(stmt)
org_owner_row = result.first()
if org_owner_row:
active_org_id = org_owner_row[0]
else:
@@ -86,37 +172,138 @@ async def read_users_me(
).limit(1)
result = await db.execute(stmt)
org_row = result.first()
active_org_id = org_row[0] if org_row else None
# Create a response dictionary with the active_organization_id
# Get first_name and last_name from person relation if available
person = current_user.person
# Safe extraction with fallback to empty string
first_name = ""
last_name = ""
if person:
first_name = getattr(person, 'first_name', '')
last_name = getattr(person, 'last_name', '')
response_data = {
"id": current_user.id,
"email": current_user.email,
"first_name": first_name,
"last_name": last_name,
"is_active": current_user.is_active,
"region_code": current_user.region_code,
"person_id": current_user.person_id,
"role": current_user.role.value if hasattr(current_user.role, 'value') else str(current_user.role),
"subscription_plan": current_user.subscription_plan,
"scope_level": current_user.scope_level or "individual",
"scope_id": str(active_org_id) if active_org_id else None,
"ui_mode": current_user.ui_mode or "personal",
"active_organization_id": active_org_id
}
response_data = _build_user_response(current_user, active_org_id)
return UserResponse.model_validate(response_data)
@router.put("/me/person", response_model=UserResponse)
async def update_my_person(
update_data: PersonUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Frissíti a bejelentkezett felhasználó Person (és Address) rekordját.
Elfogadja a PersonUpdate sémát, amely tartalmazza a személyes, okmány és cím adatokat.
Visszaadja a frissített UserResponse objektumot.
"""
# Reload user with eager loaded person + address
stmt = (
select(User)
.where(User.id == current_user.id)
.options(joinedload(User.person).joinedload(Person.address))
)
result = await db.execute(stmt)
user = result.unique().scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
person = user.person
if not person:
raise HTTPException(status_code=400, detail="No Person record found. Complete KYC first.")
# ── Update Person fields ──
update_dict = update_data.dict(exclude_unset=True)
person_fields = ["first_name", "last_name", "phone", "mothers_last_name",
"mothers_first_name", "birth_place", "birth_date"]
address_fields = ["address_zip", "address_city", "address_street_name",
"address_street_type", "address_house_number",
"address_stairwell", "address_floor", "address_door",
"address_hrsz"]
for field in person_fields:
if field in update_dict and update_dict[field] is not None:
setattr(person, field, update_dict[field])
# ── Update identity_docs (if provided and not None) ──
if "identity_docs" in update_dict and update_dict["identity_docs"] is not None:
# Use deep copy + flag_modified to ensure SQLAlchemy detects the JSON mutation
existing_docs = copy.deepcopy(person.identity_docs) if person.identity_docs else {}
if isinstance(existing_docs, dict) and isinstance(update_dict["identity_docs"], dict):
existing_docs.update(update_dict["identity_docs"])
person.identity_docs = existing_docs
flag_modified(person, "identity_docs")
else:
person.identity_docs = update_dict["identity_docs"]
flag_modified(person, "identity_docs")
# ── Update Address fields ──
has_address_update = any(f in update_dict for f in address_fields)
if has_address_update:
# Use GeoService to get or create address
addr_id = await GeoService.get_or_create_full_address(
db,
zip_code=update_dict.get("address_zip"),
city=update_dict.get("address_city"),
street_name=update_dict.get("address_street_name"),
street_type=update_dict.get("address_street_type"),
house_number=update_dict.get("address_house_number"),
parcel_id=update_dict.get("address_hrsz"),
)
if addr_id:
person.address_id = addr_id
try:
await db.commit()
# Refresh to get the latest state
await db.refresh(person)
if person.address:
await db.refresh(person.address)
except SQLAlchemyError as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Build and return the full response
response_data = _build_user_response(user)
return UserResponse.model_validate(response_data)
@router.put("/me/password", status_code=200)
async def change_my_password(
change_data: ChangePasswordRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Jelszó módosítás a bejelentkezett felhasználó számára.
Ellenőrzi a jelenlegi jelszót, validálja az új jelszó komplexitását,
majd elmenti az új hash-t. Válaszként egy {'status': 'ok', 'message': ...} dict-et ad.
"""
# 1. Ellenőrizzük a jelenlegi jelszót
if not verify_password(change_data.current_password, current_user.hashed_password):
raise HTTPException(
status_code=400,
detail="A jelenlegi jelszó helytelen."
)
# 2. Ellenőrizzük, hogy az új jelszó nem egyezik-e a régivel
if verify_password(change_data.new_password, current_user.hashed_password):
raise HTTPException(
status_code=400,
detail="Az új jelszó nem egyezhet meg a jelenlegi jelszóval."
)
# 3. Jelszó komplexitás ellenőrzése (dinamikus admin beállítások alapján)
await AuthService._validate_password_complexity(db, change_data.new_password, current_user.region_code)
# 4. Új jelszó hash-elése és mentése
current_user.hashed_password = get_password_hash(change_data.new_password)
try:
await db.commit()
await db.refresh(current_user)
except SQLAlchemyError as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
return {"status": "ok", "message": "Jelszó sikeresen megváltoztatva."}
@router.get("/me/trust")
async def get_user_trust(
db: AsyncSession = Depends(get_db),
@@ -125,10 +312,10 @@ async def get_user_trust(
) -> Dict[str, Any]:
"""
Visszaadja a felhasználó Gondos Gazda Index (Trust Score) értékét.
A számítás dinamikusan betölti a paramétereket a SystemParameter rendszerből
(Global/Country/Region/User hierarchia).
Paraméterek:
- force_recalculate: Ha True, akkor újraszámolja a trust score-t
(alapértelmezetten cache-elt értéket ad vissza, ha kevesebb mint 24 órája számoltuk)
@@ -154,28 +341,29 @@ async def update_user_preferences(
update_dict = update_data.dict(exclude_unset=True)
if not update_dict:
raise HTTPException(status_code=400, detail="No fields to update")
# Validate ui_mode if present
if "ui_mode" in update_dict:
if update_dict["ui_mode"] not in ["personal", "fleet"]:
raise HTTPException(status_code=422, detail="ui_mode must be 'personal' or 'fleet'")
# Update user fields
for field, value in update_dict.items():
if hasattr(current_user, field):
setattr(current_user, field, value)
else:
raise HTTPException(status_code=400, detail=f"Invalid field: {field}")
try:
await db.commit()
await db.refresh(current_user)
except SQLAlchemyError as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Return the Pydantic model instead of raw SQLAlchemy object
return UserResponse.model_validate(current_user)
response_data = _build_user_response(current_user)
return UserResponse.model_validate(response_data)
@router.patch("/me/active-organization", response_model=UserWithTokenResponse)
@@ -186,18 +374,18 @@ async def update_active_organization(
):
"""
Update the user's active organization (scope_id).
Accepts an organization_id (UUID/string) or None to revert to personal mode.
Returns a new JWT token with updated scope_id in the payload.
"""
# Extract organization_id from request
org_id = update_data.organization_id
# Validate that the user has access to this organization if org_id is provided
if org_id is not None:
from sqlalchemy import select
from app.models.marketplace.organization import OrganizationMember
# Check if user is a member of the organization
stmt = select(OrganizationMember).where(
OrganizationMember.organization_id == org_id,
@@ -205,23 +393,23 @@ async def update_active_organization(
)
result = await db.execute(stmt)
member = result.scalar_one_or_none()
if not member:
raise HTTPException(
status_code=403,
detail="You are not a member of this organization"
)
# Update user's scope_id
current_user.scope_id = org_id
try:
await db.commit()
await db.refresh(current_user)
except SQLAlchemyError as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Generate new JWT token with updated scope_id
role_key = current_user.role.value.upper()
token_payload = {
@@ -232,16 +420,18 @@ async def update_active_organization(
"scope_id": org_id,
"person_id": str(current_user.person_id) if current_user.person_id else None,
}
access_token, _ = create_tokens(data=token_payload)
# Return user data with new token
response_data = _build_user_response(current_user)
return UserWithTokenResponse(
user=UserResponse.model_validate(current_user),
user=UserResponse.model_validate(response_data),
access_token=access_token,
token_type="bearer"
)
@router.get("/me/network", response_model=NetworkResponse)
async def get_my_network(
db: AsyncSession = Depends(get_db),
@@ -257,7 +447,7 @@ async def get_my_network(
l1_stmt = select(User).where(User.referred_by_id == current_user.id)
l1_result = await db.execute(l1_stmt)
l1_users = l1_result.scalars().all()
level1 = [
NetworkMemberL1(
email=u.email,
@@ -267,7 +457,7 @@ async def get_my_network(
)
for u in l1_users
]
# L2: L1 userek által meghívottak
level2 = []
if l1_users:
@@ -275,7 +465,7 @@ async def get_my_network(
l2_stmt = select(User).where(User.referred_by_id.in_(l1_ids))
l2_result = await db.execute(l2_stmt)
l2_users = l2_result.scalars().all()
level2 = [
NetworkMemberL2L3(
referral_code=u.referral_code or "",
@@ -283,7 +473,7 @@ async def get_my_network(
)
for u in l2_users
]
# L3: L2 userek által meghívottak
level3 = []
if l2_users:
@@ -291,7 +481,7 @@ async def get_my_network(
l3_stmt = select(User).where(User.referred_by_id.in_(l2_ids))
l3_result = await db.execute(l3_stmt)
l3_users = l3_result.scalars().all()
level3 = [
NetworkMemberL2L3(
referral_code=u.referral_code or "",
@@ -299,7 +489,7 @@ async def get_my_network(
)
for u in l3_users
]
return NetworkResponse(
level1=level1,
level2=level2,