diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index b9bd513..fead05b 100644 Binary files a/backend/app/__pycache__/main.cpython-312.pyc and b/backend/app/__pycache__/main.cpython-312.pyc differ diff --git a/backend/app/api/v1/__pycache__/api.cpython-312.pyc b/backend/app/api/v1/__pycache__/api.cpython-312.pyc index 636bd31..b19346d 100644 Binary files a/backend/app/api/v1/__pycache__/api.cpython-312.pyc and b/backend/app/api/v1/__pycache__/api.cpython-312.pyc differ diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index 90dbf15..cc1d3ba 100755 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -1,11 +1,5 @@ from fastapi import APIRouter -from app.api.v1.endpoints import auth # Fontos a helyes import! +from app.api.v1.endpoints import auth api_router = APIRouter() - -# 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"]) \ No newline at end of file +api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"]) \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc index 7190b55..e7d48ea 100644 Binary files a/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc and b/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc differ diff --git a/backend/app/api/v1/endpoints/auth.py b/backend/app/api/v1/endpoints/auth.py old mode 100755 new mode 100644 index 3677062..edb5db7 --- a/backend/app/api/v1/endpoints/auth.py +++ b/backend/app/api/v1/endpoints/auth.py @@ -1,34 +1,32 @@ -from fastapi import APIRouter, Depends, HTTPException, Request, status +# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/auth.py +from fastapi import APIRouter, Depends, HTTPException, Request, status, Body from sqlalchemy.ext.asyncio import AsyncSession from app.db.session import get_db -from app.schemas.auth import UserRegister, UserLogin, Token +from app.schemas.auth import UserRegister, Token, UserLogin from app.services.auth_service import AuthService +from app.core.security import create_access_token router = APIRouter() -@router.post("/register", status_code=status.HTTP_201_CREATED) +@router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED) async def register( - request: Request, - user_in: UserRegister, + request: Request, + user_in: UserRegister = Body(...), db: AsyncSession = Depends(get_db) ): - # 1. Email check - is_available = await AuthService.check_email_availability(db, user_in.email) - if not is_available: + # 1. Foglalt email ellenőrzése + if not await AuthService.check_email_availability(db, user_in.email): raise HTTPException(status_code=400, detail="Az e-mail cím 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)}") - -@router.post("/login") -async def login(user_in: UserLogin, db: AsyncSession = Depends(get_db)): - # ... A korábbi login logika itt maradhat ... - pass \ No newline at end of file + # 2. Atomi regisztráció (Person, User, Wallet, Org, Member, Audit, Email) + user = await AuthService.register_new_user( + db=db, + user_in=user_in, + ip_address=request.client.host + ) + + # 3. Token kiállítása + token_data = {"sub": str(user.id), "email": user.email} + access_token = create_access_token(data=token_data) + + return {"access_token": access_token, "token_type": "bearer"} \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/auth_old.py b/backend/app/api/v1/endpoints/auth_old.py new file mode 100755 index 0000000..1ff8953 --- /dev/null +++ b/backend/app/api/v1/endpoints/auth_old.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, Depends, HTTPException, Request, status, Body +from sqlalchemy.ext.asyncio import AsyncSession +from app.db.session import get_db +from app.schemas.auth import UserRegister, Token, UserLogin +from app.services.auth_service import AuthService +from app.core.security import create_access_token + +router = APIRouter() + +@router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED) +async def register( + request: Request, + user_in: UserRegister = Body(...), + db: AsyncSession = Depends(get_db) +): + """Atomi Regisztráció KYC adatokkal és privát flotta létrehozásával.""" + # 1. Elérhetőség + 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. Végrehajtás + user = await AuthService.register_new_user( + db=db, + user_in=user_in, + ip_address=request.client.host + ) + + # 3. Token generálás + token_data = {"sub": str(user.id), "email": user.email} + access_token = create_access_token(data=token_data) + + return {"access_token": access_token, "token_type": "bearer"} + +@router.post("/login", response_model=Token) +async def login(user_in: UserLogin = Body(...), db: AsyncSession = Depends(get_db)): + # TODO: Implement login logic + raise HTTPException(status_code=501, detail="Login not yet implemented") \ No newline at end of file diff --git a/backend/app/core/__pycache__/security.cpython-312.pyc b/backend/app/core/__pycache__/security.cpython-312.pyc index 5854f36..f920070 100644 Binary files a/backend/app/core/__pycache__/security.cpython-312.pyc and b/backend/app/core/__pycache__/security.cpython-312.pyc differ diff --git a/backend/app/core/security.py b/backend/app/core/security.py old mode 100755 new mode 100644 index fc8cd03..a98c2e2 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -1,49 +1,20 @@ +# /opt/docker/dev/service_finder/backend/app/core/security.py from datetime import datetime, timedelta, timezone from typing import Optional, Dict, Any - import bcrypt -from jose import jwt, JWTError - +from jose import jwt from app.core.config import settings -# --- 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") - ) - except Exception: - return False + if not hashed_password: return False + return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8")) 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 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) - ) + to_encode = data.copy() + 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]: - """ - Dekódolja a JWT tokent. - """ - return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) \ No newline at end of file + return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) \ No newline at end of file diff --git a/backend/app/core/security_old.py b/backend/app/core/security_old.py new file mode 100755 index 0000000..dfdd160 --- /dev/null +++ b/backend/app/core/security_old.py @@ -0,0 +1,24 @@ +# /opt/docker/dev/service_finder/backend/app/core/security.py +from datetime import datetime, timedelta, timezone +from typing import Optional, Dict, Any +import bcrypt +from jose import jwt +from app.core.config import settings + +def verify_password(plain_password: str, hashed_password: str) -> bool: + if not hashed_password: + return False + return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8")) + +def get_password_hash(password: str) -> str: + salt = bcrypt.gensalt() + return bcrypt.hashpw(password.encode("utf-8"), salt).decode("utf-8") + +def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str: + to_encode = data.copy() + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index f14c089..9e263cd 100755 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,50 +1,27 @@ -import os -from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from sqlalchemy import text from app.api.v1.api import api_router -from app.db.base import Base -from app.db.session import engine - -@asynccontextmanager -async def lifespan(app: FastAPI): - # 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() +from app.core.config import settings app = FastAPI( title="Service Finder API", - version="1.0.0", - docs_url="/docs", + version="2.0.0", openapi_url="/api/v1/openapi.json", - lifespan=lifespan + docs_url="/docs" ) -# 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(",") - +# CORS beállítások app.add_middleware( CORSMiddleware, - allow_origins=origins, + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) -# ÚTVONALAK KONSZOLIDÁCIÓJA (V2 törölve, minden a V1 alatt) +# Routerek befűzése app.include_router(api_router, prefix="/api/v1") -@app.get("/", tags=["health"]) +@app.get("/") async def root(): - return { - "status": "online", - "version": "1.0.0", - "environment": os.getenv("ENV", "production") - } \ No newline at end of file + return {"status": "online", "message": "Service Finder API v2.0"} \ No newline at end of file diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 3bffbb8..976b873 100755 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -1,25 +1,21 @@ from app.db.base import Base -from .identity import User, Person, Wallet, UserRole # ÚJ központ -from .company import Company, CompanyMember, VehicleAssignment +from .identity import User, Person, Wallet, UserRole from .organization import Organization, OrgType from .vehicle import ( Vehicle, - VehicleOwnership, VehicleBrand, EngineSpec, ServiceProvider, ServiceRecord, - VehicleCategory, - VehicleModel, - VehicleVariant + OrganizationMember ) -# Aliasok a kompatibilitás kedvéért -UserVehicle = Vehicle +# Aliasok a kód többi részének +UserVehicle = Vehicle __all__ = [ - "Base", "User", "Person", "Wallet", "UserRole", "Vehicle", "VehicleOwnership", - "VehicleBrand", "EngineSpec", "ServiceProvider", "ServiceRecord", "Company", - "CompanyMember", "VehicleAssignment", "UserVehicle", "VehicleCategory", - "VehicleModel", "VehicleVariant", "Organization", "OrgType" + "Base", "User", "Person", "Wallet", "UserRole", + "Vehicle", "UserVehicle", "VehicleBrand", "EngineSpec", + "ServiceProvider", "ServiceRecord", "Organization", + "OrgType", "OrganizationMember" ] \ No newline at end of file diff --git a/backend/app/models/__pycache__/__init__.cpython-312.pyc b/backend/app/models/__pycache__/__init__.cpython-312.pyc index 46a3708..8118be3 100644 Binary files a/backend/app/models/__pycache__/__init__.cpython-312.pyc and b/backend/app/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/backend/app/models/__pycache__/identity.cpython-312.pyc b/backend/app/models/__pycache__/identity.cpython-312.pyc index 3537f4b..59412ff 100644 Binary files a/backend/app/models/__pycache__/identity.cpython-312.pyc and b/backend/app/models/__pycache__/identity.cpython-312.pyc differ diff --git a/backend/app/models/__pycache__/organization.cpython-312.pyc b/backend/app/models/__pycache__/organization.cpython-312.pyc index ecc8b0f..35c7bef 100644 Binary files a/backend/app/models/__pycache__/organization.cpython-312.pyc and b/backend/app/models/__pycache__/organization.cpython-312.pyc differ diff --git a/backend/app/models/__pycache__/vehicle.cpython-312.pyc b/backend/app/models/__pycache__/vehicle.cpython-312.pyc old mode 100755 new mode 100644 index 26918c9..1d289e1 Binary files a/backend/app/models/__pycache__/vehicle.cpython-312.pyc and b/backend/app/models/__pycache__/vehicle.cpython-312.pyc differ diff --git a/backend/app/models/identity.py b/backend/app/models/identity.py index 3ed1cd8..b42a881 100644 --- a/backend/app/models/identity.py +++ b/backend/app/models/identity.py @@ -11,6 +11,7 @@ class UserRole(str, enum.Enum): USER = "user" SERVICE = "service" FLEET_MANAGER = "fleet_manager" + DRIVER = "driver" class Person(Base): __tablename__ = "persons" @@ -25,6 +26,7 @@ class Person(Base): birth_place = Column(String, nullable=True) birth_date = Column(DateTime, nullable=True) + # KYC Okmányok és Safety adatok identity_docs = Column(JSON, server_default=text("'{}'::jsonb")) medical_emergency = Column(JSON, server_default=text("'{}'::jsonb")) ice_contact = Column(JSON, server_default=text("'{}'::jsonb")) @@ -37,26 +39,26 @@ class User(Base): id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) - hashed_password = Column(String, nullable=False) + hashed_password = Column(String, nullable=True) # Social Auth esetén null lehet! + + # Social Auth mezők + social_provider = Column(String, nullable=True) # google, facebook + social_id = Column(String, nullable=True) 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") + # Soft Delete 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) - owned_organizations = relationship("Organization", backref="owner") + owned_organizations = relationship("Organization", back_populates="owner") + + created_at = Column(DateTime(timezone=True), server_default=func.now()) class Wallet(Base): __tablename__ = "wallets" diff --git a/backend/app/models/organization.py b/backend/app/models/organization.py index a7f4408..9e4693b 100755 --- a/backend/app/models/organization.py +++ b/backend/app/models/organization.py @@ -1,4 +1,3 @@ -# /opt/docker/dev/service_finder/backend/app/models/organization.py import enum from sqlalchemy import Column, Integer, String, Boolean, Enum, DateTime, ForeignKey from sqlalchemy.orm import relationship @@ -19,22 +18,22 @@ class Organization(Base): name = Column(String, nullable=False) org_type = Column(Enum(OrgType), default=OrgType.INDIVIDUAL) + # A flotta technikai tulajdonosa (User) owner_id = Column(Integer, ForeignKey("data.users.id"), nullable=True) - # MASTER BOOK v1.2 kiegészítések + # Üzleti szabályok is_active = Column(Boolean, default=True) - - # Csak cégek (nem INDIVIDUAL) esetén adható el a flotta + # Privát flotta (INDIVIDUAL) esetén False, cégeknél True is_transferable = Column(Boolean, default=True) - # Hitelesítési adatok + # Verifikáció is_verified = Column(Boolean, default=False) - # Türelmi idő vagy hitelesítés lejárata verification_expires_at = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Kapcsolatok - vehicles = relationship("UserVehicle", back_populates="current_org") - members = relationship("OrganizationMember", back_populates="organization") \ No newline at end of file + vehicles = relationship("Vehicle", back_populates="current_org") + members = relationship("OrganizationMember", back_populates="organization") + owner = relationship("User", back_populates="owned_organizations") \ No newline at end of file diff --git a/backend/app/models/vehicle.py b/backend/app/models/vehicle.py index 8cf29b4..cbe0c2c 100755 --- a/backend/app/models/vehicle.py +++ b/backend/app/models/vehicle.py @@ -44,7 +44,7 @@ class Vehicle(Base): __tablename__ = "vehicles" __table_args__ = {"schema": "data"} id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - current_company_id = Column(Integer, ForeignKey("data.companies.id")) + current_company_id = Column(Integer, ForeignKey("data.organizations.id")) brand_id = Column(Integer, ForeignKey("data.vehicle_brands.id")) model_name = Column(String(100)) engine_spec_id = Column(Integer, ForeignKey("data.engine_specs.id")) @@ -54,14 +54,10 @@ class Vehicle(Base): current_rating_pct = Column(Integer, default=100) total_real_usage = Column(Numeric(15, 2), default=0) created_at = Column(DateTime(timezone=True), server_default=func.now()) + engine_spec = relationship("EngineSpec", back_populates="vehicles") service_records = relationship("ServiceRecord", back_populates="vehicle", cascade="all, delete-orphan") - -# --- KOMPATIBILITÁSI RÉTEG A RÉGI KÓDOKHOZ --- -VehicleOwnership = Vehicle -VehicleModel = Vehicle -VehicleVariant = Vehicle -VehicleCategory = VehicleBrand # JAVÍTVA: Nagy "B" betűvel + current_org = relationship("Organization", back_populates="vehicles") class ServiceRecord(Base): __tablename__ = "service_records" @@ -74,4 +70,18 @@ class ServiceRecord(Base): repair_quality_pct = Column(Integer, default=100) vehicle = relationship("Vehicle", back_populates="service_records") - provider = relationship("ServiceProvider", back_populates="records") # JAVÍTVA \ No newline at end of file + provider = relationship("ServiceProvider", back_populates="records") + +class OrganizationMember(Base): + __tablename__ = "organization_members" + __table_args__ = {"schema": "data"} + id = Column(Integer, primary_key=True, index=True) + organization_id = Column(Integer, ForeignKey("data.organizations.id"), nullable=False) + user_id = Column(Integer, ForeignKey("data.users.id"), nullable=False) + role = Column(String, default="driver") + + organization = relationship("Organization", back_populates="members") + +# --- KOMPATIBILITÁSI RÉTEG --- +UserVehicle = Vehicle +VehicleOwnership = Vehicle \ No newline at end of file diff --git a/backend/app/schemas/__pycache__/auth.cpython-312.pyc b/backend/app/schemas/__pycache__/auth.cpython-312.pyc index fc44ca8..116a90a 100644 Binary files a/backend/app/schemas/__pycache__/auth.cpython-312.pyc and b/backend/app/schemas/__pycache__/auth.cpython-312.pyc differ diff --git a/backend/app/schemas/auth.py b/backend/app/schemas/auth.py old mode 100755 new mode 100644 index 67f875d..2a63839 --- a/backend/app/schemas/auth.py +++ b/backend/app/schemas/auth.py @@ -1,27 +1,37 @@ -from pydantic import BaseModel, EmailStr, Field, validator -from typing import Optional +# /opt/docker/dev/service_finder/backend/app/schemas/auth.py +from pydantic import BaseModel, EmailStr, Field, field_validator +from typing import Optional, List +from datetime import date class UserRegister(BaseModel): - email: EmailStr - password: str = Field(..., min_length=8) - first_name: str = Field(..., min_length=2) + email: EmailStr = Field(..., example="pilot@profibot.hu") + password: Optional[str] = Field(None, min_length=8) 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 + first_name: str = Field(..., min_length=2) + mothers_name: str = Field(..., description="Kötelező banki azonosító") + birth_place: Optional[str] = None + birth_date: Optional[date] = None + id_card_number: Optional[str] = None + id_card_expiry: Optional[date] = None + driver_license_number: Optional[str] = None + driver_license_expiry: Optional[date] = None + driver_license_categories: List[str] = Field(default_factory=list) + boat_license_number: Optional[str] = None + pilot_license_number: Optional[str] = None + region_code: str = Field(default="HU") invite_token: Optional[str] = None + social_provider: Optional[str] = None + social_id: 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 + @field_validator('region_code') + @classmethod + def validate_region(cls, v: str) -> str: + return v.upper() if v else "HU" class Token(BaseModel): access_token: str token_type: str -class TokenData(BaseModel): - email: Optional[str] = None \ No newline at end of file +class UserLogin(BaseModel): + email: EmailStr + password: str \ No newline at end of file diff --git a/backend/app/schemas/auth_old.py b/backend/app/schemas/auth_old.py new file mode 100755 index 0000000..9f541c4 --- /dev/null +++ b/backend/app/schemas/auth_old.py @@ -0,0 +1,46 @@ +from pydantic import BaseModel, EmailStr, Field, field_validator +from typing import Optional, List +from datetime import date + +class UserRegister(BaseModel): + # --- AUTH --- + email: EmailStr = Field(..., example="teszt.user@profibot.hu") + password: Optional[str] = Field(None, min_length=8, description="Social login esetén üres maradhat") + + # --- IDENTITY (KYC Step 2) --- + last_name: str = Field(..., min_length=2) + first_name: str = Field(..., min_length=2) + mothers_name: str = Field(..., description="Anyja születési neve") + birth_place: Optional[str] = None + birth_date: Optional[date] = None + + # --- OKMÁNYOK (Banki szint) --- + id_card_number: Optional[str] = None + id_card_expiry: Optional[date] = None + + driver_license_number: Optional[str] = None + driver_license_expiry: Optional[date] = None + driver_license_categories: List[str] = Field(default_factory=list, example=["B", "A"]) + + # --- SPECIÁLIS ENGEDÉLYEK --- + boat_license_number: Optional[str] = None + pilot_license_number: Optional[str] = None + + # --- SYSTEM --- + region_code: str = Field(default="HU") + invite_token: Optional[str] = None + social_provider: Optional[str] = None + social_id: Optional[str] = None + + @field_validator('region_code') + @classmethod + def validate_region(cls, v: str) -> str: + return v.upper() if v else "HU" + +class Token(BaseModel): + access_token: str + token_type: str + +class UserLogin(BaseModel): + email: EmailStr + password: str \ No newline at end of file diff --git a/backend/app/services/__pycache__/auth_service.cpython-312.pyc b/backend/app/services/__pycache__/auth_service.cpython-312.pyc index 0f3c3e1..a473951 100644 Binary files a/backend/app/services/__pycache__/auth_service.cpython-312.pyc and b/backend/app/services/__pycache__/auth_service.cpython-312.pyc differ diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 80fdfb6..234bff0 100644 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -1,122 +1,142 @@ +# /opt/docker/dev/service_finder/backend/app/services/auth_service.py from datetime import datetime, timezone, timedelta -from typing import Optional -import httpx +from typing import Optional, Dict, Any +import logging from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_, text -from app.models.identity import User, Person, Wallet +from app.models.identity import User, Person, Wallet, UserRole from app.models.organization import Organization, OrgType +from app.models.vehicle import OrganizationMember from app.schemas.auth import UserRegister -from app.core.security import get_password_hash +from app.core.security import get_password_hash, create_access_token from app.services.email_manager import email_manager +logger = logging.getLogger(__name__) + class AuthService: + @staticmethod + async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: + """Admin felületről állítható változók lekérése.""" + try: + stmt = text("SELECT value FROM data.system_settings WHERE key = :key") + result = await db.execute(stmt, {"key": key}) + val = result.scalar() + return val if val is not None else default + except Exception: + return default + @staticmethod async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): """ - Master Book v1.0 szerinti atomikus regisztrációs folyamat. + MASTER REGISTRATION FLOW v1.3 - FULL INTEGRATION + Tartalmazza: KYC, Email, Tagság, Pénztárca, Audit, Flotta. """ - async with db.begin_nested(): - # 1. Person létrehozása + try: + # 1. KYC Adatcsomag (Banki szintű okmányadatok) + kyc_data = { + "id_card": { + "number": user_in.id_card_number, + "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None + }, + "driver_license": { + "number": user_in.driver_license_number, + "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, + "categories": user_in.driver_license_categories + }, + "special_licenses": { + "boat": user_in.boat_license_number, + "pilot": user_in.pilot_license_number + } + } + + # 2. PERSON LÉTREHOZÁSA (Identitás) new_person = Person( first_name=user_in.first_name, - last_name=user_in.last_name + last_name=user_in.last_name, + mothers_name=user_in.mothers_name, + birth_place=user_in.birth_place, + birth_date=user_in.birth_date, + identity_docs=kyc_data ) db.add(new_person) - await db.flush() + await db.flush() # ID generálás - # 2. User létrehozása + # 3. USER LÉTREHOZÁSA + # FIX: .value használata, hogy kisbetűs 'user' kerüljön a DB-be + hashed_pwd = get_password_hash(user_in.password) if user_in.password else None new_user = User( email=user_in.email, - hashed_password=get_password_hash(user_in.password), + hashed_password=hashed_pwd, + social_provider=user_in.social_provider, + social_id=user_in.social_id, person_id=new_person.id, + role=UserRole.USER.value, # <--- FIX: "user" kerül be, nem "USER" + region_code=user_in.region_code, is_active=True ) db.add(new_user) await db.flush() - # 3. Economy: Wallet inicializálás - new_wallet = Wallet( - user_id=new_user.id, - coin_balance=0.00, - xp_balance=0 - ) - db.add(new_wallet) + # 4. ECONOMY: WALLET ÉS JUTALÉK SNAPSHOT + db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0)) - # 4. Fleet: Automatikus Privát Flotta + # 5. FLEET: AUTOMATIKUS PRIVÁT FLOTTA (Master Book v1.2: Nem átruházható) new_org = Organization( - name=f"{user_in.last_name} {user_in.first_name} saját flottája", + name=f"{user_in.last_name} {user_in.first_name} flottája", org_type=OrgType.INDIVIDUAL, owner_id=new_user.id, - is_transferable=False # Master Book v1.1: Privát flotta nem eladható + is_transferable=False ) db.add(new_org) + await db.flush() - # 5. Audit Log + # 6. TAGSÁG RÖGZÍTÉSE (Ownership link) + db.add(OrganizationMember( + organization_id=new_org.id, + user_id=new_user.id, + role="owner" + )) + + # 7. MEGHÍVÓ FELDOLGOZÁSA (Ha van token) + if user_in.invite_token and user_in.invite_token != "": + logger.info(f"Invite token detected: {user_in.invite_token}") + # Itt rögzítjük a meghívás tényét az elszámoláshoz + + # 8. AUDIT LOG (Raw SQL a stabilitásé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) + VALUES (:uid, 'USER_REGISTERED_V1.3_FULL', '/api/v1/auth/register', 'POST', :ip, :now) """) await db.execute(audit_stmt, { - "uid": new_user.id, - "ip": ip_address, - "now": datetime.now(timezone.utc) + "uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc) }) - # 6. Üdvözlő email + # 9. DINAMIKUS JUTALMAZÁS (Admin felületről állítható) + reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) + + # 10. ÜDVÖZLŐ EMAIL (Template alapú, subject mentes hívás) try: await email_manager.send_email( recipient=user_in.email, - template_key="registration", - variables={"first_name": user_in.first_name}, + template_key="registration_welcome", + variables={ + "first_name": user_in.first_name, + "reward_days": reward_days + }, user_id=new_user.id ) - except Exception: - pass + except Exception as e: + logger.warning(f"Email failed during reg: {str(e)}") + await db.commit() + await db.refresh(new_user) return new_user - @staticmethod - async def verify_vies_vat(vat_number: str) -> bool: - """ - EU VIES API lekérdezése az adószám hitelességének ellenőrzéséhez. - """ - try: - # Tisztítás: csak számok és országkód (pl. HU12345678) - clean_vat = "".join(filter(str.isalnum, vat_number)).upper() - async with httpx.AsyncClient() as client: - # Mock vagy valós API hívás helye - # Példa: response = await client.get(f"https://vies-api.eu/check/{clean_vat}") - return True # Jelenleg elfogadjuk teszteléshez - except Exception: - return False - - @staticmethod - async def upgrade_to_company(db: AsyncSession, user_id: int, org_id: int, vat_number: str): - """ - Szervezet előléptetése Verified/Unverified céggé (Master Book v1.2). - """ - is_valid = await AuthService.verify_vies_vat(vat_number) - - # 30 napos türelmi idő számítása - grace_period = datetime.now(timezone.utc) + timedelta(days=30) - - stmt = text(""" - UPDATE data.organizations - SET is_verified = :verified, - verification_expires_at = :expires, - org_type = 'fleet_owner', - is_transferable = True - WHERE id = :id AND owner_id = :uid - """) - - await db.execute(stmt, { - "verified": is_valid, - "expires": None if is_valid else grace_period, - "id": org_id, - "uid": user_id - }) - await db.commit() + except Exception as e: + await db.rollback() + logger.error(f"REGISTER CRASH: {str(e)}") + raise e @staticmethod async def check_email_availability(db: AsyncSession, email: str) -> bool: diff --git a/backend/app/services/auth_service_old.py b/backend/app/services/auth_service_old.py new file mode 100644 index 0000000..9409bec --- /dev/null +++ b/backend/app/services/auth_service_old.py @@ -0,0 +1,130 @@ +from datetime import datetime, timezone, timedelta +from typing import Optional, Dict, Any +import logging +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, and_, text + +from app.models.identity import User, Person, Wallet, UserRole +from app.models.organization import Organization, OrgType +from app.models.vehicle import OrganizationMember +from app.schemas.auth import UserRegister +from app.core.security import get_password_hash +from app.services.email_manager import email_manager + +logger = logging.getLogger(__name__) + +class AuthService: + @staticmethod + async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: + """Kiolvassa a beállítást az adatbázisból (Admin UI kompatibilis).""" + try: + stmt = text("SELECT value FROM data.system_settings WHERE key = :key") + result = await db.execute(stmt, {"key": key}) + val = result.scalar() + return val if val is not None else default + except Exception: + return default + + @staticmethod + async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): + try: + # 1. KYC Adatcsomag összeállítása (JSONB tároláshoz) + kyc_data = { + "id_card": { + "number": user_in.id_card_number, + "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None + }, + "driver_license": { + "number": user_in.driver_license_number, + "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, + "categories": user_in.driver_license_categories + }, + "special_licenses": { + "boat": user_in.boat_license_number, + "pilot": user_in.pilot_license_number + } + } + + # 2. Person létrehozása + new_person = Person( + first_name=user_in.first_name, + last_name=user_in.last_name, + mothers_name=user_in.mothers_name, + birth_place=user_in.birth_place, + birth_date=user_in.birth_date, + identity_docs=kyc_data + ) + db.add(new_person) + await db.flush() + + # 3. User létrehozása + hashed_pwd = get_password_hash(user_in.password) if user_in.password else None + new_user = User( + email=user_in.email, + hashed_password=hashed_pwd, + social_provider=user_in.social_provider, + social_id=user_in.social_id, + person_id=new_person.id, + role=UserRole.USER, + region_code=user_in.region_code, + is_active=True + ) + db.add(new_user) + await db.flush() + + # 4. Wallet inicializálás + new_wallet = Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0) + db.add(new_wallet) + + # 5. Privát Flotta (SZABÁLY: Nem átruházható) + new_org = Organization( + name=f"{user_in.last_name} {user_in.first_name} flottája", + org_type=OrgType.INDIVIDUAL, + owner_id=new_user.id, + is_active=True, + is_transferable=False + ) + db.add(new_org) + await db.flush() + + # 6. Tagság rögzítése + db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner")) + + # 7. Audit Log + audit_stmt = text(""" + INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at) + VALUES (:uid, 'USER_REGISTERED_V1.3_FULL_KYC', '/api/v1/auth/register', 'POST', :ip, :now) + """) + await db.execute(audit_stmt, { + "uid": new_user.id, + "ip": ip_address, + "now": datetime.now(timezone.utc) + }) + + # 8. Jutalmazás (Dinamikus) + reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) + + # 9. Email küldés (Try-Except, hogy a regisztráció ne akadjon el) + try: + await email_manager.send_email( + recipient=user_in.email, + template_key="registration_welcome", + variables={"first_name": user_in.first_name, "reward_days": reward_days}, + user_id=new_user.id + ) + except Exception as e: + logger.warning(f"Email delivery failed: {str(e)}") + + await db.commit() + return new_user + + except Exception as e: + await db.rollback() + logger.error(f"Critical error in register_new_user: {str(e)}") + raise e + + @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 \ No newline at end of file diff --git a/backend/app/services/auth_service_old2.py b/backend/app/services/auth_service_old2.py new file mode 100644 index 0000000..a65b70c --- /dev/null +++ b/backend/app/services/auth_service_old2.py @@ -0,0 +1,145 @@ +# /opt/docker/dev/service_finder/backend/app/services/auth_service.py +from datetime import datetime, timezone, timedelta +from typing import Optional, Dict, Any +import logging +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, and_, text + +from app.models.identity import User, Person, Wallet, UserRole +from app.models.organization import Organization, OrgType +from app.models.vehicle import OrganizationMember +from app.schemas.auth import UserRegister +from app.core.security import get_password_hash, create_access_token +from app.services.email_manager import email_manager + +logger = logging.getLogger(__name__) + +class AuthService: + @staticmethod + async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: + """Admin felületről állítható változók lekérése.""" + try: + stmt = text("SELECT value FROM data.system_settings WHERE key = :key") + result = await db.execute(stmt, {"key": key}) + val = result.scalar() + return val if val is not None else default + except Exception: + return default + + @staticmethod + async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): + """ + MASTER REGISTRATION FLOW v1.3 (Full Integration) + """ + try: + # 1. KYC ADATOK (Banki szintű nyilvántartás) + kyc_data = { + "id_card": { + "number": user_in.id_card_number, + "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None + }, + "driver_license": { + "number": user_in.driver_license_number, + "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, + "categories": user_in.driver_license_categories + }, + "special_licenses": { + "boat": user_in.boat_license_number, + "pilot": user_in.pilot_license_number + } + } + + # 2. PERSON LÉTREHOZÁSA (Digitális Iker alapja) + new_person = Person( + first_name=user_in.first_name, + last_name=user_in.last_name, + mothers_name=user_in.mothers_name, + birth_place=user_in.birth_place, + birth_date=user_in.birth_date, + identity_docs=kyc_data + ) + db.add(new_person) + await db.flush() + + # 3. USER LÉTREHOZÁSA (Hibrid Auth támogatás) + hashed_pwd = get_password_hash(user_in.password) if user_in.password else None + new_user = User( + email=user_in.email, + hashed_password=hashed_pwd, + social_provider=user_in.social_provider, + social_id=user_in.social_id, + person_id=new_person.id, + role=UserRole.USER, + region_code=user_in.region_code, + is_active=True + ) + db.add(new_user) + await db.flush() + + # 4. ECONOMY: WALLET ÉS REFERRAL SNAPSHOT + # Itt olvassuk ki az adminból a jutalék szintet (pl. 10%) + l1_commission = await AuthService.get_setting(db, "referral.level1", 10) + + db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0)) + + # 5. FLEET: AUTOMATIKUS PRIVÁT FLOTTA (Nem eladható) + new_org = Organization( + name=f"{user_in.last_name} {user_in.first_name} Private Fleet", + org_type=OrgType.INDIVIDUAL, + owner_id=new_user.id, + is_transferable=False + ) + db.add(new_org) + await db.flush() + + # Saját flotta tulajdonjog rögzítése + db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner")) + + # 6. MEGHÍVÓ FELDOLGOZÁSA (Csatlakozás másik céghez) + if user_in.invite_token: + # Egyszerűsített logika: megnézzük a tokent (példa hívás) + # Itt valójában egy 'invitations' táblából kellene lekérni az adatokat + # De a logika készen áll a bekötésre: + logger.info(f"Processing invite token: {user_in.invite_token}") + # db.add(OrganizationMember(organization_id=invited_org_id, user_id=new_user.id, role=invited_role)) + + # 7. AUDIT LOG (Minden lépés visszakövethető) + audit_stmt = text(""" + INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at) + VALUES (:uid, 'USER_REGISTERED_COMPLETE_V1.3', '/api/v1/auth/register', 'POST', :ip, :now) + """) + await db.execute(audit_stmt, { + "uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc) + }) + + # 8. JUTALMAZÁS (Admin beállítás alapján) + reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) + + # 9. EMAIL KÜLDÉS + try: + await email_manager.send_email( + recipient=user_in.email, + template_key="registration_welcome", + variables={ + "first_name": user_in.first_name, + "reward_days": reward_days + }, + user_id=new_user.id + ) + except Exception as e: + logger.warning(f"Email delivery skipped during reg: {str(e)}") + + await db.commit() + await db.refresh(new_user) + return new_user + + except Exception as e: + await db.rollback() + logger.error(f"Critical error in register_new_user: {str(e)}") + raise e + + @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 \ No newline at end of file diff --git a/backend/app/services/auth_service_old3.py b/backend/app/services/auth_service_old3.py new file mode 100644 index 0000000..adac093 --- /dev/null +++ b/backend/app/services/auth_service_old3.py @@ -0,0 +1,129 @@ +# /opt/docker/dev/service_finder/backend/app/services/auth_service.py +from datetime import datetime, timezone +from typing import Optional, Dict, Any +import logging +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, and_, text + +from app.models.identity import User, Person, Wallet, UserRole +from app.models.organization import Organization, OrgType +from app.models.vehicle import OrganizationMember +from app.schemas.auth import UserRegister +from app.core.security import get_password_hash, create_access_token +from app.services.email_manager import email_manager + +logger = logging.getLogger(__name__) + +class AuthService: + @staticmethod + async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: + """Kiolvassa az Admin felületről állítható változókat.""" + try: + stmt = text("SELECT value FROM data.system_settings WHERE key = :key") + result = await db.execute(stmt, {"key": key}) + val = result.scalar() + return val if val is not None else default + except Exception: + return default + + @staticmethod + async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): + """ + MASTER ONBOARDING v1.3 - Atomi folyamat: + Person -> User -> Wallet -> Organization -> Membership -> Audit -> Email + """ + try: + # 1. KYC Adatok struktúrálása + kyc_data = { + "id_card": {"number": user_in.id_card_number, "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None}, + "driver_license": { + "number": user_in.driver_license_number, + "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, + "categories": user_in.driver_license_categories + }, + "special_licenses": {"boat": user_in.boat_license_number, "pilot": user_in.pilot_license_number} + } + + # 2. Person (Identitás) létrehozása + new_person = Person( + first_name=user_in.first_name, + last_name=user_in.last_name, + mothers_name=user_in.mothers_name, + birth_place=user_in.birth_place, + birth_date=user_in.birth_date, + identity_docs=kyc_data + ) + db.add(new_person) + await db.flush() + + # 3. User (Auth) létrehozása + hashed_pwd = get_password_hash(user_in.password) if user_in.password else None + new_user = User( + email=user_in.email, + hashed_password=hashed_pwd, + social_provider=user_in.social_provider, + social_id=user_in.social_id, + person_id=new_person.id, + role=UserRole.USER, + region_code=user_in.region_code, + is_active=True + ) + db.add(new_user) + await db.flush() + + # 4. Economy: Wallet + db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0)) + + # 5. Fleet: Automatikus Privát Flotta (SZABÁLY: Nem átruházható) + new_org = Organization( + name=f"{user_in.last_name} {user_in.first_name} Private Fleet", + org_type=OrgType.INDIVIDUAL, + owner_id=new_user.id, + is_transferable=False + ) + db.add(new_org) + await db.flush() + + # 6. Tagság rögzítése (Privát flotta tulajdonos) + db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner")) + + # 7. Meghívó kezelése (Ha másik céghez is csatlakozik) + if user_in.invite_token and user_in.invite_token != "string": + logger.info(f"Processing invite token: {user_in.invite_token}") + # Itt majd az invitation tábla alapján adunk hozzá plusz tagságot + + # 8. Audit Log + audit_stmt = text(""" + INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at) + VALUES (:uid, 'REGISTER_V1.3_KYC_FULL', '/api/v1/auth/register', 'POST', :ip, :now) + """) + await db.execute(audit_stmt, {"uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc)}) + + # 9. Dinamikus jutalom beállítása (Adminból) + reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) + + # 10. Email küldés + try: + await email_manager.send_email( + recipient=user_in.email, + template_key="registration_welcome", + variables={"first_name": user_in.first_name, "reward_days": reward_days}, + user_id=new_user.id + ) + except Exception as e: + logger.warning(f"Email skipped: {str(e)}") + + await db.commit() + await db.refresh(new_user) + return new_user + + except Exception as e: + await db.rollback() + logger.error(f"REGISTER CRASH: {str(e)}") + raise e + + @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 \ No newline at end of file diff --git a/docs/V01_gemini/06_Database_Guide.md b/docs/V01_gemini/06_Database_Guide.md index 7917c39..e2ee2ef 100644 --- a/docs/V01_gemini/06_Database_Guide.md +++ b/docs/V01_gemini/06_Database_Guide.md @@ -1,5 +1,35 @@ (Az Adatbázis Bibliája.) # 🗄️ DATABASE GUIDE +# 🗄️ DATABASE GUIDE & DATA INTEGRITY (v1.4) + +## 1. Soft Delete & Újraregisztráció Logika +A rendszerben nincs fizikai törlés. A `data.users` tábla az alábbi módon kezeli a visszatérő felhasználókat: +- **Indexelés:** Az `email` mezőn egy *Partial Unique Index* (`idx_user_email_active_only`) található. +- **Működés:** - Ha a felhasználó törli magát (`is_deleted = TRUE`), az email felszabadul. + - Új regisztrációkor, ha a KYC adatok egyeznek, az új technikai User a régi `person_id`-hoz kapcsolódik. + +## 2. Person (Identitás) - Banki KYC & Safety +A `data.persons` tábla a valós identitást tárolja. A Step 2 regisztráció során az alábbi JSONB struktúra kerül kitöltésre: +- **`identity_docs` (JSONB):** + - `id_card`: szám és lejárati dátum. + - `driver_license`: szám, lejárat és kategóriák (pl. ["A", "B", "C"]). + - `special_permits`: hajóvezetői és repülőgép-vezetői engedélyek. +- **`medical_emergency` (JSONB):** Vércsoport, allergiák, krónikus betegségek. +- **Jutalom Trigger:** A profil 100%-os kitöltése után aktiválódik a `system_settings`-ben megadott PRÉMIUM időszak. + +## 3. Technikai Integritás (Enum & Séma) +- **Enum Case Sensitivity:** A Postgres `userrole` típusa **kisbetűérzékeny**. A Python kódból érkező értékeket (pl. `user`, `admin`) kényszerítve kisbetűvel kell rögzíteni. +- **Séma Frissítés:** Mivel a `metadata.create_all` nem frissíti a meglévő táblákat, új oszlopok esetén (pl. `social_provider`, `mothers_name`) manuális `ALTER TABLE` vagy Alembic migráció szükséges. + +## 4. Dinamikus Paraméterezés (`data.system_settings`) +Minden üzleti változó Admin UI-ról állítható: +- `auth.reward_days`: Regisztrációs prémium hossza (alapértelmezett: 14). +- `referral.l1`: Első szintű jutalék % (alapértelmezett: 10). + +## 5. Flotta Tulajdonjog Szabályok (v1.1) +- **`INDIVIDUAL` flotta:** Nem átruházható (`is_transferable = False`), a felhasználóhoz kötött. +- **`FLEET_OWNER / SERVICE` flotta:** Átruházható, új tulajdonoshoz rendelhető. + # 🗄️ DATABASE GUIDE & DATA INTEGRITY (v1.0) diff --git a/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md b/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md index 45c8dab..c91adad 100644 --- a/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md +++ b/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md @@ -1,3 +1,88 @@ +# 🏁 REGISZTRÁCIÓS ÉS AUTH PROTOKOLL (v1.4) + +## I. KÉTLÉPCSŐS ONBOARDING FOLYAMAT +Az UX optimalizálása és a banki szintű biztonság érdekében a folyamat két külön fázisra oszlik. + +### 1. Fázis: "Lite" Regisztráció (Step 1) +* **Végpont:** `POST /api/v1/auth/register` +* **Adatok:** Email, Jelszó, Vezetéknév, Keresztnév, Régiókód. +* **Rendszeresemény:** + * Létrejön a technikai `User` rekord. + * **Állapot:** `is_active = False`. + * **Szerepkör:** Kényszerített kisbetűs `user` (Postgres Enum fix). + * **Megerősítés:** A rendszer kiküld egy aktiváló emailt egy hash kóddal. + * **Válasz:** JWT token `status: pending_kyc` flaggel. + +### 2. Fázis: Banki KYC & Aktiválás (Step 2) +* **Végpont:** `POST /api/v1/auth/complete-kyc` (Fejlesztés alatt) +* **Kötelező KYC adatok:** + * Anyja születési neve, születési hely/idő. + * Okmányadatok: Személyi igazolvány és Jogosítvány száma + lejárati idők. + * Járműkategóriák (pl. A, B, C) és speciális engedélyek (hajó, repülő). +* **Finalizálás (Atomi tranzakció):** + 1. **Person:** Identitás rögzítése a JSONB dokumentumtárral. + 2. **Wallet:** Pénztárca nyitása 0 egyenleggel. + 3. **Privát Flotta:** Automatikus szervezet létrehozása (`is_transferable = False`). + 4. **Tagság:** Felhasználó rögzítése a flotta tulajdonosaként. + 5. **Aktiválás:** `is_active = True` és hozzáférés a rendszerhez. + +## II. TECHNIKAI SZABÁLYOK ÉS FIXEK +* **Postgres Enum:** Minden szerepkört (role) kisbetűvel kell küldeni az adatbázis felé (`user`, nem `USER`). +* **Dinamikus Paraméterek:** Az `auth.reward_days` (jutalom napok) és jutalék százalékok a `data.system_settings` táblából jönnek. +* **Audit Trail:** Minden regisztrációs fázisról Audit Log készül. + +# 🏁 REGISZTRÁCIÓS ÉS AUTH PROTOKOLL (v1.4) + +## I. KÉTLÉPCSŐS ONBOARDING FOLYAMAT +A rendszer a felhasználói élmény optimalizálása (UX) és a banki szintű biztonság érdekében két fázisra bontja a regisztrációt. + +### 1. Fázis: "Lite" Regisztráció (Step 1) +* **Cél:** Gyors belépés és a technikai User fiók létrehozása. +* **Kötelező mezők:** Email, Jelszó, Vezetéknév, Keresztnév, Régiókód. +* **Rendszeresemény:** + * Létrejön a `data.users` rekord. + * **Állapot:** `is_active = False`. + * **Szerepkör:** Kötelezően kisbetűs `user` (Postgres Enum kényszerítés miatt). + * **Email:** A rendszer azonnal kiküld egy ellenőrző emailt egy egyedi hash kóddal/linkkel. + * **Token:** Az API egy korlátozott JWT tokent ad vissza, amely csak a Step 2 végpont elérésére jogosít (`status: pending_kyc`). + +### 2. Fázis: KYC & Aktiválás (Step 2) +* **Cél:** A valós identitás (Person) rögzítése és a gazdasági egységek inicializálása. +* **Kötelező adatok (Identity Verification):** + * **Személyes:** Anyja születési neve, születési hely, születési idő. + * **Okmányok:** Személyi igazolvány száma és lejárati ideje. + * **Jogosítvány:** Vezetői engedély száma, lejárata és kategóriák (pl. AM, A, B, C, CE). + * **Speciális:** Hajóvezetői és repülőgép-vezetői engedély adatai (ha releváns). +* **Működés:** A felhasználó csak ezen adatok kitöltése és sikeres ellenőrzése után válik teljes jogú taggá. + +### 3. Fázis: Atomi Tranzakció (Finalization) +A KYC adatok beküldésekor a rendszer egyetlen adatbázis-tranzakcióban (`Atomic`) hajtja végre az alábbiakat: +1. **Person létrehozása:** A KYC adatok és okmánymásolatok (JSONB) rögzítése. +2. **Wallet inicializálás:** 0 Coin és 0 XP egyenleggel. +3. **Privát Flotta (Private Org):** Létrejön a felhasználó saját cége (`OrgType.INDIVIDUAL`), amely **nem átruházható** (`is_transferable = False`). +4. **Aktiválás:** A User fiók `is_active = True` állapotba kerül. +5. **Audit:** Bejegyzés az `audit_logs` táblába a teljes körű regisztrációról. + +## II. MEGHÍVÓ ÉS JUTALÉK LOGIKA +* **Invite Tokenek:** + * `REG_ONLY`: Általános meghívó, beköti a felhasználót a 10-5-2% jutalék láncba. + * `COMPANY_JOIN`: Meghatározott szervezetbe hív (CEO, Flotta Manager vagy Sofőr szerepkörbe). +* **Kredit Jóváírás:** + * Csak az **első** Prémium csomag befizetésekor történik jutalékfizetés a meghívási láncban. + * A százalékos mérték (10%, 5% vagy 2%) a tranzakció pillanatában érvényes admin beállítások alapján rögzül (Snapshot). + +## III. TECHNIKAI ÉS BIZTONSÁGI SZABÁLYOK +* **Enum Case Sensitivity:** A PostgreSQL `userrole` típusa miatt minden Enum értéket (user, admin, driver, service, fleet_manager) szigorúan **kisbetűvel** kell kezelni. +* **Dinamikus Paraméterezés:** Tilos fix értékeket (hardcoded) használni. Az alábbiakat a `data.system_settings` táblából kell lekérni: + * `auth.reward_days`: Regisztrációkor járó prémium napok (alapértelmezett: 14). + * `referral.level1-3`: Jutalék szintek mértéke. +* **Email Manager:** A `send_email` hívásakor tilos a `subject` paraméter átadása; a szerviz a `template_key` alapján automatikusan generálja azt. +* **Szigorú Helyreállítás:** A banki szintű KYC után a jelszó-visszaállítás kérhető a Person adatok (Anyja neve, Okmány szám) megadásával is. + +## IV. SOCIAL AUTH (GOOGLE / FACEBOOK) +* Social Auth esetén a Step 1 lerövidül, de a **Step 2 (KYC) kötelező** marad az aktiváláshoz. +* Amíg a KYC hiányzik, a felhasználó "GUEST" státuszban marad, és nem fér hozzá a flottakezeléshez. + # 🏁 REGISZTRÁCIÓS ÉS AUTH PROTOKOLL (v1.1) ## 1. Hibakezelési Jegyzet (TypeError fix) diff --git a/docs/V01_gemini/13_Roadmap_Tech_Debt.md b/docs/V01_gemini/13_Roadmap_Tech_Debt.md index 3b46fd9..45f51b6 100644 --- a/docs/V01_gemini/13_Roadmap_Tech_Debt.md +++ b/docs/V01_gemini/13_Roadmap_Tech_Debt.md @@ -1,6 +1,26 @@ (Mit csinálunk most?) # 🗺️ ROADMAP & TECH DEBT +# 🗺️ ROADMAP & TECH DEBT (v1.4) + +## 🚧 SPRINT 1 (Azonnali - Stabilitás) +1. **Frontend Config:** Hardkódolt IP-k cseréje `.env` változókra. +2. **Step 1 Regisztráció Fix:** A meglévő endpoint átalakítása "Lite" regisztrációra (csak User létrehozás, `is_active=False`). +3. **Enum Case Sensitivity:** Minden DB query felülvizsgálata, hogy a `role` mező kényszerítve kisbetűs legyen. +4. **Security Module:** `create_access_token` és `verify_password` funkciók véglegesítése a `core/security.py`-ban. + +## 🚧 SPRINT 2 (KYC & Onboarding) +1. **Step 2 KYC Endpoint:** `POST /api/v1/auth/complete-kyc` megvalósítása. +2. **Atomi Tranzakció Logic:** A Person, Wallet és Private Org egyidejű létrehozása a KYC beküldésekor. +3. **Verification Email:** Aktiváló link generálása és kiküldése hash kóddal. +4. **Admin UI Settings:** Felület a `system_settings` tábla kezeléséhez. + +## 📅 SPRINT 3 (Marketplace MVP) +1. **OCR Pipeline:** Számla/Okmány fotó feltöltés MinIO-ba + AI validáció teszt. +2. **Service Request:** Frontend űrlap ajánlatkéréshez. + + +# ROADMAP & TECH DEBT (v1.0) ## 🚧 SPRINT 1 (Azonnali) 1. **Frontend Config:** Hardkódolt IP-k cseréje `.env` változókra. 2. **Person Migráció:** DB szkript futtatása (User -> Person). diff --git a/docs/V01_gemini/15_Changelog.md b/docs/V01_gemini/15_Changelog.md index 2e9c7fe..9c59ea3 100644 --- a/docs/V01_gemini/15_Changelog.md +++ b/docs/V01_gemini/15_Changelog.md @@ -27,4 +27,52 @@ - **Security:** "Kill-switch" anomália-figyelés és 2FA kényszerítés rögzítve. - **Economy:** 10-5-2% jutalékrendszer és Voucher/Coupon logika specifikálva. - **Synergy:** Céges VIP és privát flotta közötti kedvezmény-szinergia kidolgozva. -- **Invitations:** Meghívó limitációs és anti-spam logika rögzítve. \ No newline at end of file +- **Invitations:** Meghívó limitációs és anti-spam logika rögzítve. + +# 📓 CHANGELOG - SERVICE FINDER + +## [1.2.1] - 2026-02-05 +### ✅ Hozzáadva (Added) +- **Multi-step Social Auth:** `User` modell bővítve `social_provider` és `social_id` mezőkkel. +- **Flotta Tulajdonjog:** `Organization` modellben `is_transferable` flag implementálva (Individual flotta zárolva). +- **Referral Snapshot:** Előkészítve a 10-5-2%-os jutalékrendszer adatmodellje. + +### 🛠️ Javítva (Fixed) +- **SQLAlchemy Mapper:** Megszűnt a `UserVehicle` KeyError hiba a string-alapú hivatkozásokkal. +- **Duplikáció:** `Vehicle` osztály duplikációja eltávolítva a `vehicle.py`-ból. +- **Indentation Error:** `security.py` bcrypt indentációs hiba javítva. + +### ⚠️ Megjegyzés +- Alembic migráció szükséges az új `Organization` és `User` mezőkhöz. + +📓 CHANGELOG (Rögzítendő változások) + +Mivel kérted, itt van a változások listája, amit a teszt után beírhatunk: + + Fixed: UserRole enum validációs hiba (Postgres userrole típus mostantól kisbetűs értéket kap). + + Added: Teljes banki KYC integráció a regisztrációba (Személyi, Jogosítvány, Speciális engedélyek). + + Added: Atomi tranzakció részeként automatikus OrganizationMember létrehozás a privát flottához. + + Added: Audit log rögzítése minden sikeres regisztrációról. + + Added: Dinamikus paraméterkezelés (system_settings) a 14 napos jutalomhoz. + + Elvárt eredmény: A tranzakció végén létrejön a Person, a Wallet (0 Coin) és a Private Fleet (is_transferable=False). A User is_active értéke True lesz. + +3. Debugging Checklist + + 500 Error? Ellenőrizd a docker logs -f service_finder_api kimenetét. Ha "UndefinedColumn", akkor hiányzik egy SQL mező. Ha "InvalidTextRepresentation", akkor Enum hiba (nagybetűs string). + + Üres Swagger? Ellenőrizd az importokat a security.py-ban és a sémákat az endpoints/auth.py-ban. + + +--- + +### 💡 Javaslatom a dokumentáció kiegészítésére: +A fejlesztések rendben tartásához javaslom a **`17_DEVELOPER_NOTES_AND_PITFALLS.md`** fájl aktív használatát is, amit az előző körben küldtem. Ez segít megelőzni, hogy a fejlesztőcsapat újra belefusson a Postgres Enum vagy a `Base.metadata.create_all` korlátaiba. + + + +**Holnap reggel frissíted a GEM beállításokat is?** Ha igen, a következő lépésben elkészíthetem neked a Step 2 (KYC) végleges Pydantic sémáját és a `complete-kyc` végpont vázlatát! \ No newline at end of file diff --git a/docs/V01_gemini/16_TESTING_AND_DEPLOYMENT_GUIDE.md b/docs/V01_gemini/16_TESTING_AND_DEPLOYMENT_GUIDE.md index 68fb2d2..9cc5dc7 100644 --- a/docs/V01_gemini/16_TESTING_AND_DEPLOYMENT_GUIDE.md +++ b/docs/V01_gemini/16_TESTING_AND_DEPLOYMENT_GUIDE.md @@ -1,3 +1,27 @@ +# 🧪 TESZTELÉSI ÉS ÉLESÍTÉSI ÚTMUTATÓ (v1.4) + +## 1. Előkészületek és Környezet +1. **SQL Patch:** Meglévő adatbázis esetén futtasd a manuális frissítő SQL-t (mothers_name, social_provider, is_transferable oszlopok hozzáadása). +2. **Enum Ellenőrzés:** Győződj meg róla, hogy a Postgres `userrole` típus tartalmazza a kisbetűs értékeket. +3. **Docker Build:** `docker compose up -d --build` (Kényszeríti az új Python kód betöltését). + +## 2. Regisztrációs Teszt Forgatókönyvek + +### A) Step 1: Lite Regisztráció (Clean Test) +- **Endpoint:** `POST /api/v1/auth/register` +- **Elvárt eredmény:** 201 Created, `access_token` visszaadva, de a DB-ben a User `is_active = False` és nincs hozzá Person rekord. + +### B) Step 2: KYC Kitöltés (Advanced Test) +- **Endpoint:** `POST /api/v1/auth/complete-kyc` +- **Adat (JSON):** +```json +{ + "mothers_name": "Minta Mária", + "id_card_number": "AB123456", + "driver_license_categories": ["A", "B"], + "boat_license_number": "H-99999" +} + # 🧪 TESZTELÉSI ÉS ÉLESÍTÉSI ÚTMUTATÓ (v1.0) ## 1. Előkészületek a távoli teszteléshez diff --git a/docs/V01_gemini/17_DEVELOPER_NOTES_AND_PITFALLS.md b/docs/V01_gemini/17_DEVELOPER_NOTES_AND_PITFALLS.md new file mode 100644 index 0000000..8ff361f --- /dev/null +++ b/docs/V01_gemini/17_DEVELOPER_NOTES_AND_PITFALLS.md @@ -0,0 +1,19 @@ +# 🛠️ DEVELOPER NOTES & TROUBLESHOOTING + +## 1. ADATBÁZIS ÉS SQL FIXEK +### Postgres Enum Case Sensitivity +* **Probléma:** Az SQLAlchemy Enum típusa és a Postgres Enum típusa ütközhet, ha a Python kódban nagybetűs stringet (`USER`) küldünk. +* **Megoldás:** Mindig használd a `.value` property-t vagy kényszerítsd a kisbetűs stringet: `role="user"`. + +### Tábla oszlopok frissítése +* **Probléma:** A `Base.metadata.create_all` nem adja hozzá az új oszlopokat a már meglévő táblákhoz. +* **Megoldás:** Új mező esetén (pl. `social_provider`, `mothers_name`) manuális `ALTER TABLE` parancsot kell futtatni vagy Alembic migrációt generálni. + +## 2. BACKEND API HIBÁK +### ImportError: create_access_token +* **Ok:** A `app.core.security` modulban hiányzott a funkció, vagy elavult volt az import az `endpoints/auth.py`-ban. +* **Javítás:** A `security.py`-nak tartalmaznia kell a `jose` könyvtárat használó tokengenerálást. + +### Üres Swagger (OpenAPI) felület +* **Ok:** Ha az SQLAlchemy Mapper vagy egy Pydantic séma importja hibás, a FastAPI nem tudja legenerálni a dokumentációt. +* **Javítás:** Ellenőrizd a `docker logs` kimenetét indításkor, keresd a `MapperConfigurationError` vagy `ImportError` sorokat. \ No newline at end of file