Save test environment changes

This commit is contained in:
2026-02-04 21:58:57 +00:00
parent 5dd5692d83
commit a57d5333d4
67 changed files with 1603 additions and 239 deletions

BIN
backend/app/__pycache__/main.cpython-312.pyc Executable file → Normal file

Binary file not shown.

BIN
backend/app/api/v1/__pycache__/api.cpython-312.pyc Executable file → Normal file

Binary file not shown.

View File

@@ -1,12 +1,11 @@
from fastapi import APIRouter
from app.api.v1.endpoints import auth, users, vehicles, billing, fleet, expenses, reports
from app.api.v1.endpoints import auth # Fontos a helyes import!
api_router = APIRouter()
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(billing.router, prefix="/billing", tags=["billing"])
api_router.include_router(vehicles.router, prefix="/vehicles", tags=["vehicles"])
api_router.include_router(fleet.router, prefix="/fleet", tags=["fleet"])
api_router.include_router(expenses.router, prefix="/expenses", tags=["expenses"])
api_router.include_router(reports.router, prefix="/reports", tags=["reports"])
# Minden auth funkciót ide gyűjtünk (Register, Login, Recover)
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
# Itt jönnek majd a további modulok:
# api_router.include_router(users.router, prefix="/users", tags=["Users"])
# api_router.include_router(fleet.router, prefix="/fleet", tags=["Fleet"])

Binary file not shown.

View File

@@ -1,91 +1,34 @@
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text
from datetime import datetime, timedelta
import hashlib, secrets
from app.db.session import get_db
from app.models.user import User
from app.core.security import get_password_hash
from app.services.email_manager import email_manager
from app.services.config_service import config
from app.schemas.auth import UserRegister, UserLogin, Token
from app.services.auth_service import AuthService
router = APIRouter()
@router.post("/register")
@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register(
request: Request,
email: str,
password: str,
first_name: str,
last_name: str,
request: Request,
user_in: UserRegister,
db: AsyncSession = Depends(get_db)
):
ip = request.client.host
# 1. BOT-VÉDELEM
throttle_min = await config.get_setting('registration_throttle_minutes', default=10)
check_throttle = await db.execute(text("""
SELECT count(*) FROM data.audit_logs
WHERE ip_address = :ip AND action = 'USER_REGISTERED' AND created_at > :t
"""), {'ip': ip, 't': datetime.utcnow() - timedelta(minutes=int(throttle_min))})
if check_throttle.scalar() > 0:
raise HTTPException(status_code=429, detail="Túl sok próbálkozás. Várj pár percet!")
# 1. Email check
is_available = await AuthService.check_email_availability(db, user_in.email)
if not is_available:
raise HTTPException(status_code=400, detail="Az e-mail cím már foglalt.")
# 2. REGISZTRÁCIÓ
res = await db.execute(select(User).where(User.email == email))
if res.scalars().first():
raise HTTPException(status_code=400, detail="Ez az email már foglalt.")
# 2. Process
try:
user = await AuthService.register_new_user(
db=db,
user_in=user_in,
ip_address=request.client.host
)
return {"status": "success", "message": "Regisztráció sikeres!"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Szerver hiba: {str(e)}")
new_user = User(
email=email,
hashed_password=get_password_hash(password),
first_name=first_name,
last_name=last_name,
is_active=False
)
db.add(new_user)
await db.flush()
# 3. TOKEN & LOG
raw_token = secrets.token_urlsafe(48)
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
await db.execute(text("""
INSERT INTO data.verification_tokens (user_id, token_hash, token_type, expires_at)
VALUES (:u, :t, 'email_verify', :e)
"""), {'u': new_user.id, 't': token_hash, 'e': datetime.utcnow() + timedelta(days=2)})
await db.execute(text("""
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address)
VALUES (:u, 'USER_REGISTERED', '/register', 'POST', :ip)
"""), {'u': new_user.id, 'ip': ip})
# 4. EMAIL KÜLDÉS
verify_link = f"http://{request.headers.get('host')}/api/v1/auth/verify?token={raw_token}"
email_body = f"<h1>Szia {first_name}!</h1><p>Aktiváld a fiókod: <a href='{verify_link}'>{verify_link}</a></p>"
await email_manager.send_email(
recipient=email,
subject="Regisztráció megerősítése",
body=email_body,
email_type="registration",
user_id=new_user.id
)
await db.commit()
return {"message": "Sikeres regisztráció! Ellenőrizd az email fiókodat."}
@router.get("/verify")
async def verify_account(token: str, db: AsyncSession = Depends(get_db)):
token_hash = hashlib.sha256(token.encode()).hexdigest()
query = text("SELECT user_id FROM data.verification_tokens WHERE token_hash = :t AND is_used = False")
res = await db.execute(query, {'t': token_hash})
row = res.fetchone()
if not row:
raise HTTPException(status_code=400, detail="Érvénytelen aktiváló link")
await db.execute(text("UPDATE data.users SET is_active = True WHERE id = :id"), {'id': row[0]})
await db.execute(text("UPDATE data.verification_tokens SET is_used = True WHERE token_hash = :t"), {'t': token_hash})
await db.commit()
return {"message": "Fiók aktiválva!"}
@router.post("/login")
async def login(user_in: UserLogin, db: AsyncSession = Depends(get_db)):
# ... A korábbi login logika itt maradhat ...
pass

BIN
backend/app/core/__pycache__/config.cpython-312.pyc Executable file → Normal file

Binary file not shown.

BIN
backend/app/core/__pycache__/security.cpython-312.pyc Executable file → Normal file

Binary file not shown.

View File

@@ -1,48 +1,61 @@
from typing import Optional
import os
import json
from typing import Any, Optional, List
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import computed_field
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
class Settings(BaseSettings):
# --- General ---
PROJECT_NAME: str = "Traffic Ecosystem SuperApp"
VERSION: str = "2.0.0"
VERSION: str = "1.0.0"
API_V1_STR: str = "/api/v1"
DEBUG: bool = False
DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
# --- Security / JWT ---
SECRET_KEY: str
# Szigorúan .env-ből!
SECRET_KEY: str = os.getenv("SECRET_KEY", "NOT_SET_DANGER")
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 nap
# --- Password policy (TEST -> laza, PROD -> szigorú) ---
PASSWORD_MIN_LENGTH: int = 4 # TESZT: 4, ÉLES: 10-12
# --- Database & Cache ---
DATABASE_URL: str = os.getenv("DATABASE_URL")
REDIS_URL: str = os.getenv("REDIS_URL", "redis://service_finder_redis:6379/0")
# --- Database ---
DATABASE_URL: str # már nálad compose-ban meg van adva
# --- Redis ---
REDIS_URL: str = "redis://service_finder_redis:6379/0"
# --- Email sending ---
# auto = ha van SENDGRID_API_KEY -> sendgrid api, különben smtp
EMAIL_PROVIDER: str = "auto" # auto | sendgrid | smtp | disabled
EMAILS_FROM_EMAIL: str = "info@profibot.hu"
# --- Email (Auto Provider) ---
EMAIL_PROVIDER: str = os.getenv("EMAIL_PROVIDER", "auto")
EMAILS_FROM_EMAIL: str = os.getenv("EMAILS_FROM_EMAIL", "info@profibot.hu")
EMAILS_FROM_NAME: str = "Profibot"
# SMTP & SendGrid (Szigorúan .env-ből)
SENDGRID_API_KEY: Optional[str] = os.getenv("SENDGRID_API_KEY")
SMTP_HOST: Optional[str] = os.getenv("SMTP_HOST")
SMTP_PORT: int = int(os.getenv("SMTP_PORT", 587))
SMTP_USER: Optional[str] = os.getenv("SMTP_USER")
SMTP_PASSWORD: Optional[str] = os.getenv("SMTP_PASSWORD")
# SendGrid API
SENDGRID_API_KEY: Optional[str] = None
# --- External URLs ---
# .env-ben legyen átírva a .10-es IP-re!
FRONTEND_BASE_URL: str = os.getenv("FRONTEND_BASE_URL", "http://localhost:3000")
# SMTP fallback (pl. Gmail App Password vagy más szolgáltató)
SMTP_HOST: Optional[str] = None
SMTP_PORT: int = 587
SMTP_USER: Optional[str] = None
SMTP_PASSWORD: Optional[str] = None
SMTP_USE_TLS: bool = True
# Frontend base URL a linkekhez (később NPM/domain)
FRONTEND_BASE_URL: str = "http://192.168.100.43:3000"
# --- Dinamikus Admin Motor ---
async def get_db_setting(self, db: AsyncSession, key_name: str, default: Any = None) -> Any:
"""
Lekéri a paramétert a data.system_settings táblából.
Ezzel érjük el, hogy a kód újraírása nélkül, adminból lehessen
állítani a jutalom napokat, százalékokat, stb.
"""
try:
query = text("SELECT value_json FROM data.system_settings WHERE key_name = :key")
result = await db.execute(query, {"key": key_name})
row = result.fetchone()
if row and row[0] is not None:
return row[0]
return default
except Exception:
return default
# .env fájl konfigurációja
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
@@ -50,4 +63,4 @@ class Settings(BaseSettings):
extra="ignore"
)
settings = Settings()
settings = Settings()

View File

@@ -6,28 +6,44 @@ from jose import jwt, JWTError
from app.core.config import settings
# --- JELSZÓ ---
# --- JELSZÓ KEZELÉS ---
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""
Összehasonlítja a nyers jelszót a hash-elt változattal.
"""
try:
if not hashed_password:
return False
return bcrypt.checkpw(
plain_password.encode("utf-8"),
hashed_password.encode("utf-8"),
hashed_password.encode("utf-8")
)
except Exception:
return False
def get_password_hash(password: str) -> str:
"""
Biztonságos hash-t generál a jelszóból.
"""
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode("utf-8"), salt).decode("utf-8")
# --- JWT ---
# --- JWT TOKEN KEZELÉS ---
def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
"""
JWT Access tokent generál a megadott adatokkal és lejárati idővel.
"""
to_encode = dict(data)
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES))
expire = datetime.now(timezone.utc) + (
expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
def decode_token(token: str) -> Dict[str, Any]:
return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
"""
Dekódolja a JWT tokent.
"""
return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])

View File

@@ -1,45 +1,50 @@
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
import os
from app.api.v1.api import api_router
from app.api.v2.auth import router as auth_v2_router
from app.models import Base
from app.db.base import Base
from app.db.session import engine
@asynccontextmanager
async def lifespan(app: FastAPI):
from app.db.session import engine
# Séma és alap táblák ellenőrzése indításkor
async with engine.begin() as conn:
await conn.execute(text("CREATE SCHEMA IF NOT EXISTS data"))
# Base.metadata.create_all helyett javasolt az Alembic,
# de fejlesztési fázisban a run_sync biztonságos
await conn.run_sync(Base.metadata.create_all)
yield
await engine.dispose()
app = FastAPI(
title="Traffic Ecosystem SuperApp 2.0",
version="2.0.0",
openapi_url="/api/v2/openapi.json",
title="Service Finder API",
version="1.0.0",
docs_url="/docs",
openapi_url="/api/v1/openapi.json",
lifespan=lifespan
)
# BIZTONSÁG: CORS beállítások .env-ből
# Ha nincs megadva, csak a localhost-ot engedi
origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://192.168.100.43:3000", # A szerver címe a böngészőben
"http://localhost:3000", # Helyi teszteléshez
],
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ÚTVONALAK INTEGRÁCIÓJA
# ÚTVONALAK KONSZOLIDÁCIÓJA (V2 törölve, minden a V1 alatt)
app.include_router(api_router, prefix="/api/v1")
app.include_router(auth_v2_router, prefix="/api/v2/auth")
@app.get("/", tags=["health"])
async def root():
return {"status": "online", "version": "2.0.0", "docs": "/docs"}
return {
"status": "online",
"version": "1.0.0",
"environment": os.getenv("ENV", "production")
}

View File

@@ -1,6 +1,7 @@
from app.db.base import Base
from .user import User, UserRole
from .identity import User, Person, Wallet, UserRole # ÚJ központ
from .company import Company, CompanyMember, VehicleAssignment
from .organization import Organization, OrgType
from .vehicle import (
Vehicle,
VehicleOwnership,
@@ -13,12 +14,12 @@ from .vehicle import (
VehicleVariant
)
# Alias a kompatibilitás kedvéért
# Aliasok a kompatibilitás kedvéért
UserVehicle = Vehicle
__all__ = [
"Base", "User", "UserRole", "Vehicle", "VehicleOwnership", "VehicleBrand",
"EngineSpec", "ServiceProvider", "ServiceRecord", "Company",
"Base", "User", "Person", "Wallet", "UserRole", "Vehicle", "VehicleOwnership",
"VehicleBrand", "EngineSpec", "ServiceProvider", "ServiceRecord", "Company",
"CompanyMember", "VehicleAssignment", "UserVehicle", "VehicleCategory",
"VehicleModel", "VehicleVariant"
"VehicleModel", "VehicleVariant", "Organization", "OrgType"
]

BIN
backend/app/models/__pycache__/__init__.cpython-312.pyc Executable file → Normal file

Binary file not shown.

View File

@@ -0,0 +1,73 @@
# backend/app/models/identity.py
import uuid
import enum
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, JSON, Numeric, text, Enum
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from app.db.base import Base
class UserRole(str, enum.Enum):
ADMIN = "admin"
USER = "user"
SERVICE = "service"
FLEET_MANAGER = "fleet_manager"
class Person(Base):
__tablename__ = "persons"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True)
id_uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
last_name = Column(String, nullable=False)
first_name = Column(String, nullable=False)
mothers_name = Column(String, nullable=True)
birth_place = Column(String, nullable=True)
birth_date = Column(DateTime, nullable=True)
identity_docs = Column(JSON, server_default=text("'{}'::jsonb"))
medical_emergency = Column(JSON, server_default=text("'{}'::jsonb"))
ice_contact = Column(JSON, server_default=text("'{}'::jsonb"))
users = relationship("User", back_populates="person")
class User(Base):
__tablename__ = "users"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
# Technikai mezők átmentése a régi user.py-ból
role = Column(Enum(UserRole), default=UserRole.USER)
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
is_company = Column(Boolean, default=False)
company_name = Column(String, nullable=True)
tax_number = Column(String, nullable=True)
region_code = Column(String, default="HU")
is_deleted = Column(Boolean, default=False)
deleted_at = Column(DateTime, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
person_id = Column(Integer, ForeignKey("data.persons.id"), nullable=True)
person = relationship("Person", back_populates="users")
wallet = relationship("Wallet", back_populates="user", uselist=False)
# Az Organization kapcsolathoz (ha szükséges az import miatt)
owned_organizations = relationship("Organization", backref="owner")
class Wallet(Base):
__tablename__ = "wallets"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("data.users.id"), unique=True)
coin_balance = Column(Numeric(18, 2), default=0.00)
xp_balance = Column(Integer, default=0)
user = relationship("User", back_populates="wallet")

View File

@@ -1,5 +1,5 @@
import enum
from sqlalchemy import Column, Integer, String, Boolean, Enum, DateTime
from sqlalchemy import Column, Integer, String, Boolean, Enum, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.db.base import Base
@@ -10,11 +10,6 @@ class OrgType(str, enum.Enum):
FLEET_OWNER = "fleet_owner"
CLUB = "club"
class UITheme(str, enum.Enum):
LIGHT = "light"
DARK = "dark"
SYSTEM = "system"
class Organization(Base):
__tablename__ = "organizations"
__table_args__ = {"schema": "data"}
@@ -23,14 +18,11 @@ class Organization(Base):
name = Column(String, nullable=False)
org_type = Column(Enum(OrgType), default=OrgType.INDIVIDUAL)
# Új UI beállítások a V2-höz
theme = Column(Enum(UITheme), default=UITheme.SYSTEM)
logo_url = Column(String, nullable=True)
# Spec 2.2: Az owner_id a magánszemély flottájának tulajdonosát jelöli
owner_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Kapcsolatok
# members = relationship("OrganizationMember", back_populates="organization")
vehicles = relationship("UserVehicle", back_populates="current_org")
# Kapcsolatok (UserVehicle modell megléte esetén)
vehicles = relationship("UserVehicle", back_populates="current_org", cascade="all, delete-orphan")

View File

@@ -1,34 +1,6 @@
import enum
from sqlalchemy import Column, Integer, String, Boolean, Date, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.db.base import Base
class UserRole(str, enum.Enum):
ADMIN = "admin"
USER = "user"
SERVICE = "service"
FLEET_MANAGER = "fleet_manager"
class User(Base):
__tablename__ = "users"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
first_name = Column(String)
last_name = Column(String)
birthday = Column(Date, nullable=True)
role = Column(String, default=UserRole.USER)
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
is_company = Column(Boolean, default=False)
company_name = Column(String, nullable=True)
tax_number = Column(String, nullable=True)
region_code = Column(String, default="HU")
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# DEPRECATED: Minden funkció átkerült az app.models.identity modulba.
# Ez a fájl csak a kompatibilitás miatt maradt meg, de táblát nem definiál.
from .identity import User, UserRole
# Kapcsolatok
# memberships = relationship("OrganizationMember", back_populates="user", cascade="all, delete-orphan")

Binary file not shown.

View File

@@ -1,16 +1,27 @@
from pydantic import BaseModel, EmailStr, Field
from pydantic import BaseModel, EmailStr, Field, validator
from typing import Optional
class UserRegister(BaseModel):
email: EmailStr
password: str = Field(..., min_length=8)
first_name: str = Field(..., min_length=2)
last_name: str = Field(..., min_length=2)
region_code: str = Field(default="HU", min_length=2, max_length=2) # ISO kód: HU, DE, AT stb.
device_id: Optional[str] = None # Eszköz azonosító a biztonsághoz
invite_token: Optional[str] = None
@validator('region_code')
def validate_region(cls, v):
return v.upper() if v else v
# EZ HIÁNYZOTT: Az azonosításhoz (login) szükséges séma
class UserLogin(BaseModel):
email: EmailStr
password: str
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
email: Optional[str] = None
class UserRegister(BaseModel):
email: EmailStr
password: str = Field(..., min_length=8)
full_name: str
region_code: str = "HU"
device_id: str # Az eszköz egyedi azonosítója a védelemhez
email: Optional[str] = None

View File

@@ -0,0 +1,81 @@
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, text
from app.models.identity import User, Person, Wallet
from app.models.organization import Organization, OrgType
from app.schemas.auth import UserRegister
from app.core.security import get_password_hash
from app.services.email_manager import email_manager
class AuthService:
@staticmethod
async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str):
"""
Master Book v1.0 szerinti atomikus regisztrációs folyamat.
"""
# Az AsyncSession.begin() biztosítja az ATOMICitást
async with db.begin_nested(): # beágyazott tranzakció a biztonságért
# 1. Person létrehozása (Identity Level)
new_person = Person(
first_name=user_in.first_name,
last_name=user_in.last_name
)
db.add(new_person)
await db.flush() # ID generáláshoz
# 2. User létrehozása (Technical Access)
new_user = User(
email=user_in.email,
hashed_password=get_password_hash(user_in.password),
person_id=new_person.id,
is_active=True
)
db.add(new_user)
await db.flush()
# 3. Economy: Wallet inicializálás (0 Coin, 0 XP)
new_wallet = Wallet(
user_id=new_user.id,
coin_balance=0.00,
xp_balance=0
)
db.add(new_wallet)
# 4. Fleet: Automatikus Privát Flotta létrehozása
new_org = Organization(
name=f"{user_in.last_name} {user_in.first_name} saját flottája",
org_type=OrgType.INDIVIDUAL,
owner_id=new_user.id
)
db.add(new_org)
# 5. Audit Log (SQLAlchemy Core hívással a sebességért)
audit_stmt = text("""
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at)
VALUES (:uid, 'USER_REGISTERED', '/api/v1/auth/register', 'POST', :ip, :now)
""")
await db.execute(audit_stmt, {
"uid": new_user.id,
"ip": ip_address,
"now": datetime.now(timezone.utc)
})
# 6. Üdvözlő email (Subject paraméter nélkül - Spec v1.1)
try:
await email_manager.send_email(
recipient=user_in.email,
template_key="registration",
variables={"first_name": user_in.first_name},
user_id=new_user.id
)
except Exception:
pass # Email hiba ne állítsa meg a tranzakciót
return new_user
@staticmethod
async def check_email_availability(db: AsyncSession, email: str) -> bool:
query = select(User).where(and_(User.email == email, User.is_deleted == False))
result = await db.execute(query)
return result.scalar_one_or_none() is None