From 714de9dd93c89237744ca52850a61b44d06258fa Mon Sep 17 00:00:00 2001 From: Kincses Date: Fri, 6 Feb 2026 00:14:17 +0000 Subject: [PATCH] Refactor: Auth & Identity System v1.4 - Fix: Resolved SQLAlchemy Mapper error for 'UserVehicle' using string-based relationships. - Fix: Fixed Postgres Enum case sensitivity issue for 'userrole' (forcing lowercase 'user'). - Fix: Resolved ImportError for 'create_access_token' in security module. - Feature: Implemented 2-step registration protocol (Lite Register -> KYC Step). - Data: Added bank-level KYC fields (mother's name, ID/Driver/Boat/Pilot license expiry and categories). - Business: Applied private fleet isolation (is_transferable=False for individual orgs). - Docs: Updated Grand Master Book to v1.4 and added Developer Pitfalls guide. --- backend/app/__pycache__/main.cpython-312.pyc | Bin 2307 -> 992 bytes .../api/v1/__pycache__/api.cpython-312.pyc | Bin 402 -> 400 bytes backend/app/api/v1/api.py | 10 +- .../__pycache__/auth.cpython-312.pyc | Bin 1964 -> 1768 bytes backend/app/api/v1/endpoints/auth.py | 44 +++-- backend/app/api/v1/endpoints/auth_old.py | 38 ++++ .../core/__pycache__/security.cpython-312.pyc | Bin 2605 -> 2009 bytes backend/app/core/security.py | 43 +---- backend/app/core/security_old.py | 24 +++ backend/app/main.py | 39 +---- backend/app/models/__init__.py | 20 +-- .../__pycache__/__init__.cpython-312.pyc | Bin 826 -> 624 bytes .../__pycache__/identity.cpython-312.pyc | Bin 3830 -> 3647 bytes .../__pycache__/organization.cpython-312.pyc | Bin 1983 -> 2046 bytes .../__pycache__/vehicle.cpython-312.pyc | Bin 4928 -> 5521 bytes backend/app/models/identity.py | 20 ++- backend/app/models/organization.py | 15 +- backend/app/models/vehicle.py | 26 ++- .../schemas/__pycache__/auth.cpython-312.pyc | Bin 1811 -> 2537 bytes backend/app/schemas/auth.py | 44 +++-- backend/app/schemas/auth_old.py | 46 +++++ .../__pycache__/auth_service.cpython-312.pyc | Bin 4265 -> 7554 bytes backend/app/services/auth_service.py | 164 ++++++++++-------- backend/app/services/auth_service_old.py | 130 ++++++++++++++ backend/app/services/auth_service_old2.py | 145 ++++++++++++++++ backend/app/services/auth_service_old3.py | 129 ++++++++++++++ docs/V01_gemini/06_Database_Guide.md | 30 ++++ .../07_REGISTRATION_INVITATION_AND_API.md | 85 +++++++++ docs/V01_gemini/13_Roadmap_Tech_Debt.md | 20 +++ docs/V01_gemini/15_Changelog.md | 50 +++++- .../16_TESTING_AND_DEPLOYMENT_GUIDE.md | 24 +++ .../17_DEVELOPER_NOTES_AND_PITFALLS.md | 19 ++ 32 files changed, 940 insertions(+), 225 deletions(-) mode change 100755 => 100644 backend/app/api/v1/endpoints/auth.py create mode 100755 backend/app/api/v1/endpoints/auth_old.py mode change 100755 => 100644 backend/app/core/security.py create mode 100755 backend/app/core/security_old.py mode change 100755 => 100644 backend/app/models/__pycache__/vehicle.cpython-312.pyc mode change 100755 => 100644 backend/app/schemas/auth.py create mode 100755 backend/app/schemas/auth_old.py create mode 100644 backend/app/services/auth_service_old.py create mode 100644 backend/app/services/auth_service_old2.py create mode 100644 backend/app/services/auth_service_old3.py create mode 100644 docs/V01_gemini/17_DEVELOPER_NOTES_AND_PITFALLS.md diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index b9bd5132f9e0227e080ac7c3c78d81bf41a21791..fead05bec161c09bc6032978d756e24f55462b24 100644 GIT binary patch literal 992 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 Pv7};6JZo=&0(JiamQN+k diff --git a/backend/app/api/v1/__pycache__/api.cpython-312.pyc b/backend/app/api/v1/__pycache__/api.cpython-312.pyc index 636bd31208fd412fc63b93f11f700bbcaeb57e54..b19346dd1998d61501ee835e261ed812dfa721f6 100644 GIT binary patch delta 80 zcmbQlJb{_-G%qg~0}ynJwPvnm*vRL_XrT|}eqd%~WW38DdzV4=A+K0>bOYB72CnN2 hk{1~yXQ<5Ao~gYe{erFU1slH$43bwF{EPU2dH_!q7LNb` delta 55 zcmbQhJc*g_G%qg~0}#A-YR){txRK9`QQ8p5{lLt~$at4Q_AZ0!Lte4&=mxG2Yz&Ot IlM5N;0fFWX?f?J) 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 7190b5505361642b2302643e221e3899cf462a61..e7d48ea4a9cbbb8a7bba7882486db93ef8f0fa0a 100644 GIT binary patch literal 1768 zcmZuxU2GIp6ux)<_J7;$7All#>rY`#y0s`l10mT;Owkl=h)iT+8#!3Tj-U#TsEUrHN{+0`j-o1< zp9^-#39DgW7VU@=RinNv*)b=s#+`(kaFS}$-;?b&r(JFLWyMZ89cl+g0wQ>+6;@4? zbTJbo^YCkiNP@`4=O69_(sFo7Y|7wAF(XU(fhLe>piK*Alt^Zb#LYMfPb+5PZTUgW z&9!8v?JsVYQCK8*aBQ+%^$2C5{iH%lrppqeQ&R^=jvJ)nS>+N7O_HNk;(APRJ>9Fi zOd2kmwG3wQoLegydE&YN16`RRo@N#@B8wk#37sS}mdhQ>Q{^M1#3FoiqC8`9Y%Evx zX7hy3Sq1?$ZBU|nMAHoe+-V*+I1w47Ef*PqB`I+$<&sM@r)(0N#hS@z254CpqOBZ5 zRS2S4B_@~!qg5%tLNcsp;9VbwioK37blYpy4xyspV;))Ng?U^AzC8XcwXkP^4JRSO zC&Hrmt}G)r;e`UMiA`Zy43MYshDZV90w{cWq* z0e)UAwg>XVZ@>>Y{hxnk$`^!Fl9vwf^U|^sSe%mQWiw=kF9>2um}1x#DS1qqEd$j>*P~3ZOv($IQD_G@<+=J< zGs!n;1?LG;{`Xx4mR|q4-#4gNDy`QVoEsX1n^h@WCC?qyf%(}=jfI0G=ytG+aR+B% z`4in+KzEatDf3*xo~Cs4+Gy1Tu+Uz#zt3Sqc@Y?7sZvACT}G3E8+ZI#YblA zsq9)TvDV(TmfpI!@8f-|>Hd1U|87X?Q2s?yTv^x;KDYLqoBHhVr-#pPyDWVd{U%yZ z?O7PVneM+hej~kmVdD0-9aqBjSZ+niHF{8d4Bu;n!SufSG~{*q+2LpKmw5Oo>8l(9 z@#=Ow+$UY#@iNFi_F-5LWV%>rT8E2Su_(WLl#|S_IOV0G{C-e=0oZ2uD6j%L<Jj4f@LG zOOqEVer}X00l!9-azgOtGKDtBpH$(EZ|Ad5+xfzf_UhzFZfaz|-<+?}IzSQa=LX@u zFoCKz*+*XlmCq3OBrFXPWBdo2SVa@J(BLi9_Y2xnM|*CeA^80o>G+^~LAi}X?{&Y` z{r(#lH(hycb#K1DH^0z*9Z%g+P*30I>GNYhcRhJ-?(Ca)LY;URuO&JgA{TBawlrie YG!zus^pAhIE+XL&z9aB$x)b962LQUb*#H0l literal 1964 zcmZ`(U2IfE6rQ=id-ty`Z7E9)-9o`^4ZDB@h*-=b5I`uktwz^}&35m!y=Cw2GWTvv zYk>+zP4q!yA|z@84=P$nOn4#jg;Enud}#xwoH^&r zIcMg4=lm23`3a1h-<&ZnN`(B12XFYQ#NrA{$XTKjo#u#1Q`8hvf+?m%Q%XsuoRTTZ zgq&ikDb=OLoX^x!noCPLzZpmc%wQ^LhEgGyC+EUuO{#_xfiOB*b(5-PwL|MX=Z|+? zVe(MhNA0I*Q_J3??Ea=*i z^w2Up4Wr>$&F|p*v*WHauldsva>b;}9u@9pi{dpygEwo%ACYBxJpVwUYF zmYuOnR*X8qq&1q)_Atu=1vo1M%ueflG0_RWVlm#$1`G>LYIr(S7%=jV+*>%xP&be) z*@Hcd4;xuFrX_e)7bO+@>1;t~j>@ZE)e_i9g9SnF%ZeCwbzqLqz+Bu9zxNWMkY~I4 zkAw_W|JPH-eOwr)Lx7Zj4=)#I01Iz|hfjpF;ybcTPKe{86|z-NU#GfoN%XW&Q*F6F z4{@F{?C5Wt47n&>#s+dI_~aQ+m!!$+>mJ1+R^yO#Syy!RGogoUCm$S!8CXZ`uxF!< z@HHpoWpVm+&93vLtuvp7Ds5z-)VdZzgT(-?kC5h zY@?Yma?$McWix6{U*OUH!ay#Qv*R&=V*rk#GaEEwB1ezB7I}W7n9k@r2YfiHRmx_8 zC^dn^1;&`or!QoU=_^soILbh&6>Q-rJE{qanE_@jK?Gyro-sfP;CS6M{y-_{<8^mN?&WSfiu%XP~1L4UToiQQ~5QV+t#PTWK8wFaw~> z(KtJ9$1~X0i~`4EcEXU|xZW4HDs7Hq_MBQ|T6iO4#WBkq%bQ~u9ADx@T!l(|t7xPF zeAw(N@Kt!F+e$@5VJ)`W^p;JV(=T@KP4@2HI3<9z?zhK|_{ z9TUwr=&l7HS=DlG_~TOxYCSzd=R*x;5s7=DNLfY#{!?qb?+O=1BD_!*Ty8rNe6G3I lZqSvtw(q(+eC5;v5vX=9QpW9K@IG#MrD;I`ipL?ye*h=Q+>8JK 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 5854f3671071f7d6320e3f754df100b80b2337f3..f920070f0065c8da457051343acd3f62c8e54894 100644 GIT binary patch delta 800 zcmZ8fO=uHA6rRcKPm;|h*)~-hQ;D>+1~n<|!P0746@(&$3Le^6lr}SIQj=_$O)1r? z5h_?bXdJLD2}tbRU#1BL{8%2F7J|<^e>=@zw&ihf)<*cFR>WBDi-FZWre*i4;;gCJ_f!xP^LH#c}RZfI}w_qnD>L*j0i% zzQCTMJ&iBNSEJj(m>G<{lk35OT5Nl8%p4rsx;9=PoTw|wEg^ZpJ%A0N+QkI*aTpP) zpBGD>Aq8V9SF)tcl#waU(-vEmF!}rbBQ_Q66NZDsaG18Tyl}K7#EHto=;x^n_!w+e zh&pH++Z3Yi`;cE0EP`3c^60qiScJ5vgxC`lV!h%i_Z{xJhYIn3Qa8IPZo&k+E1eWl zdU1hOr66>(_fizLupd%~)WIF}1XJZOY-Lfoec}ukG#-ZCs|iMePMqd&<{<+U;m7 zPnPq1UdMAe5~udw_?8YY&_EEv@1V~FeP2Mg3A#T6bsq%wL03aaA$S8;FE@Z)Yn_L6 zqwAo_8*T?2g;mr5c3npY&HB?R!|QMrSNqpdbzrT~05-3UZ)D92V~1pG^471W+8pi= D(Ua8` delta 1488 zcmY*ZT}%{L6ux(7X4ze4clmLxKZwImzy(F4X$4a;)?#a|LV-plCS;tsEV~RlW=cuc zEc|?;D&Q-bN$!U}x-i&7sY|^Ie70g90>4IK?*evbnh*~Pkb;^KRWQ1Qrtl=Xrd&dk zIH}}%U?XAx6~1C@nv9yv%{qmLJYyQBk)0xx85!cR>B0&J2W*cXAPzUK78V`uPMe&Y zPKP{y`jwXBBL_N@svo>!(WwdUS6Gds#2TBJ^n)a8xi;~`J=ZKU!P9Ow8K63Rnnt~U z@X?v=pjN_S=Yh?lQYfS)p2h3eYj-@4C+2S4zOkz{_+U-QUtb3vk8zF;M5dqwdqLVYUl<%j$2R=K3H|W(56VoU|;$n8DpK zlKo#k<0NI;E{FHEP1qeX9iyoKiiubs9yRp54C$-0wN zlUj~jR0WMzdMjE-v#dj8pyyP7&*1Rco=blC={avuB=16LXhuU%t2RoBen9KZ&-khZ28;`tI#~ z(7D#$TWs%ry0hI*F zW!?l*sdc-NL0RD^75D@u^%y=luOfm zbeuucgy9(7EtnVp+9p^#uq37jK`?=It7r6bQ_13-$kM~Gb3lZ&04w)PeD2uoWAo(R z#kD<$ihBpyv z8|ZKm9o|4KMbz@=42o```oB{!|Hpc+@1t 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 46a370855b5ad702a1b8f14f39736f1adc4bdb48..8118be38e9c386794a7b1c113bdb81b086ad1a49 100644 GIT binary patch delta 319 zcmdnR_JM`(G%qg~0}yzNwq~j@P2`hcOqr-|Zp6rt!j!_A!CBVp#${i(`&XB^kh%rj2l3kNy;s(>nLX47JY`56MQZq7>b5bX}Gg@m4 z`4^=p=4DnTmSpDV`KIP3r50)O6)^*CEMf%_Y#@RiL~u<0z$m8517h-m2tE+O4lc57oK9b1@HMiKyAl4UggLGgs4`fP_;N+c5*49!$CL<6RYXXT6 w%#4hTcNt{vGniavFuBj5|DB1Qk?A`FkoX|Nz%SOo{fUK@o2ik#NCK!D0BmkTr~m)} literal 826 zcmaiy&ui2`6vt=>Oo| zn}33TgD3GeAbSuGy%mcTJUMS0e^3Mm^5MO2X6AkKUOsqU3#s+>{VO)q5&8njucg(& z#cLJ3K{1MPf;c7^qgadeMB_Tqxj_uxAPsI3bE_MP#cg8qCTa2(X>GMe;&7L^+#}vL zH_o9-nGEx1$YCnr7fK6yEXAvflODvEzTo)S)h9N$nth!e0Qjn1CCSswD-KOJrqNks}kMSrbas zl0^b3$3$}gPP7LqBS%6J2aW`B0MrXoeX!k2b404tBDuZv()Y$mFo**q%{TMD_sx5| z^WN^ii6=dtUsY8WV9X|N6_({y&s{>2tU?l!?wr_mP1Lrrmt;U`iaIUK8FWmO*ncD^ zHpT3>x%2Ex+n#i#mURGG*{3K+H`FYkk*Yl zKSB>c(?Lr`gNnFy70k)}R2h1g{o(2lNt|mx@FW~m@ytGUQtln{qRT#8y4sYdsF5!l z`eeRLlPGxGlKi$1us6-W0HEswaduXXv-@)J8@(3VXMGI~8cy6fUuj#7uF%q4#Zl`@ zTe)?`x13t~o@FY157Q6QHRX_%MgP?ef5RWS6RHGi{){yOQT!3ss|17VsQ6ia)j#xG z^4JTAG-~A<>_;V3Rr>|@ z;&;NHDGwLktMt6e%Xr0vIzkaO!t4GT@AEZ&OVcpqx@&Uub)eksNXu?l!h*q^eaW0_gD*&_=0JLb1Cpb-s;UYp6^9m46$C;8v4>qRA=UcbiX=2y?$L|-@u7{p`2*qg=^;wpzDV~0Z#?7-S8?|wHdX38r zfmI1Hd|=uV0_w#rm#F5pJYn_nR&eCKt7PC&i+hgHF{)TDxx&mxB7i7;X4&rVhwcDi z5HP$4AL}Te(GP_q^k?fE=AB@Sp5o8(hI5!c-NMA+f8Zy9fIGUO^uL!1;;D|ww#;*z#9<@;1z(CLATo0hd;J=$#y+bq2SEKP`O<2BI}lxasd+*R z99Li47Q<_{W{)P0t5-lDZDzH=L`UbFK~2ms-5*&CvA#c}&g|^>v%a1A#R=vS!?}C* zWpuGaB59jeh*@=Ast6{DURjISD4<|E7ZL!SI1XT6P`S2KZ> zF)&2QR*GnfPX5X~1*o!*#Y65EOK5Rw(JkTp^1RfP`23>u#JtR^#FEVXykb91xyd(K zzOvt91xXf7KF2D_#tSr~_|@dMtS)vR*%$=GCZx}-TA_bg-s(We6@IS<51^o+_=NHm zELQ|{8$2J1$;=2}k#a@MxWOMNBP=;Xe1+;2A%h0*o0Id|Je52*-RT#4Z@PUg2?Tbe(*bZJDqe&{9T_#a2M#12ZEd{|Tpj1x@0 NGDtDHGZsk$WdI!!Rx|(r delta 184 zcmeyzzn`D?G%qg~0}xo8Y0kX3k(ZT;kz=wjlO5x{%|T4_8CfcYG=(Q~vP@y+4lPbC znmmuigV|40b}|d=*GX)WYv&2PF~37DeAjJ_X@v9gU3xisSDEj2l%e=`8T>x=3!qZ>i?~k(+$oB}J zdEN@>cd6~M)+^=(H>~APemHLcLjZ{@07Q-IR4*GjCM#WK* z;cE;0R(jw15xNaP^`cG$i|w0+#udIym*OK2xL(JvHWZ(a_s!zTcuHH0&ugF~^o1~;994GtI33!?OmNv-u78iU$ zR?TOZR1+P*f=rw6pm}62T8?(nO3#c9N9%tb*1Cg3)*^Q_zKv=;vLU`G9BD#ER3u$) zo_bWBxvSp6y0dvZu^Fb~Ghuy#+yxQp2) zDCp+KC1lmopD;5;{HF#@GPCrUhOm(xAH%cLD&`rrH9N6FCEU%Rhr!$-geghKD6D`z zXE)h4dBd*G!uXryZvSvO5ZsMSR9sQ|K!P(jzLtFFFZ(X+T&W15?9e`E&jr>`Rk)t) zVAbJY7q&7LM{s%Mpb9IXc*$G)GZ-bOWktc~4om9XP1^D;bxKQ2tGd)6WA{^Z4O7}2 zc`19WOVrHXG5wJ2$f1jN7=lkx4X{`Xa4-mSgEhcn&10cj!C3<={%`@&mF4yvp07Zv GH}V&Uc;>qR delta 393 zcmbQJeL#)xG%qg~0}vP-tIllUoyaG__-vy3L9JA=C~+W`0Ak5h;V3C426u)O{uYK5 zfmET@%peU63{ldRteS$G6&UAnFdo{xpYt#yOQnpa?BrS8I{a0FDTyVCddc~@1&Mi? zsgrMV?`8JWl$$(JKzZ^ho{y84unA1Q!OO?^Ve)fc6L$B@njV)WJtr&i&1Jkf`2e2; zXQO|S7SM_!x5@9AB{#G2i!d^#PL>w%VG9C@22aiv@L-DoF(W7M7RX`@o2(|N$mlfL zLvR_Brp#nskxs^l$Y$yQaRC1^Q 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 fc44ca8029f3fd1c436f986cf93a958ac0fd3247..116a90ae7d44a7b0248c4a1bcf5dc3b2bc06b3b9 100644 GIT binary patch literal 2537 zcmb7FO=ufO6yB9)W&QczvgLm#ic`yuf0DE$rAeALKh2+IQYTOZVOj5v?QQl)nO!-N zNkPyA9D1mHs(kY?#V!;IJr_bxy%^R9d7+fjL&+_v-Ahk>Z+Gn|h8{YgZ{~gP>CKy& z_ul@}*cj(vRED3bw*`*-1BLwovNv{@VfuzsIEB|ZozL@nC?C?p`LIs%ghxK433?{+k6Gfz zK9(#xZ7NiAqgQlE)n;wx3RhLCDQ@#TPU4cJsfuKqEE{s8GX+~U4M}s!E!DEKyh}hu zWj_obBZ08H$Kx3(m**8OAG!;X)3CxTq0R6{$hXN8D$s~R&VlG@1tqfCaG-6VQ5x$8 z9oR$9@25%P_7yEO+~6L8Hz1T6vAQfJp1 zaIJNAZ2{L-XV)HZ?R9n?$lbNh3Cuq0%U zy2-m1WuMXo$PvmWmRk#|X4)4E%$!%}OnYLn2$|DBA4z&aqpm#p-@X^s+YZPQa9~I$}HbgVoqiDqFB%*8Ql5;Cbiq7DxxegMKp@~9A$50 z`lz6?lABVPxrHB^k=8!<rqzgRdVxd-zppEBem*y8yeO;<7_-RP-KP6y3Ne>ZVfED6EsB_@F3hm{g+3 zutBp%1OnhTi=t#0rY&LDvq0a1ss>brEt|166x$I}2%QKR35y}55wLqO43YH$xI}}R zVErg#zOw;@L4>0S8H8g9!wA?*-GmHjZ|T%tG!=FNm~S~Yj@mJV#6F;{brYWEDYw(v zTfMZEJ-6L?zI;7s}2%3{TOraR=!PO7JR`uWN2)Oh*&isg`T z?|;uf+U}ev->8HgGO?5HugY7K)7$A>`Bo+4kX-G^aQS8>UCpfaJ7gH88sT zVt`K&6h-ovd2GjP5~)$p?~9FqB6=&0OtgiK9nc@^S?k@fH^4yNbf zzi`?ObCFhrW3LH_{ypOrzx9d3iomgpZ&v?_gJw0RjBknh z{~fac(Z7i~hDCSFTu=?aA~5%J*~sBIu~7Uty@(G5`vGbZFte>B00f(8|8l0*)cR!F zj}_IqY$J60k|G(lDl@!buLAvo^vWOt_oNyNUiEpk<3_Pf+p13ea`P&|yBu%Rw*zsb z=lqhtz&-@$*ugBccopV({!i|r!(IH1JMt^ny+^wEk;?mf987zsLVU-{^*s)zy=aJU MtHl0>b(OjzpTvUREp5G+9`$-Pui2 zqgJxiAUWcIIVHy)gOUS(0(UM*j;drOgep!E7m&?`6YtI1sT)xVM%r)Res6Z>n{VFC zPknu90`2#`r(QuNcAR3tSQ+Pux1ZyN!9{3-NUAwC<-qle_-D5ruj)B4TZ9vvGGZ%BgsWU*^ct`V z;W|^8x~J_b$#1s7Q;^krvPMtVl-bm4vX;!6$61RR%-BmG`;(UW4CYzZ&hRWxorW0Z z>+5iRJzRfhoPL(srHMK=ut9c{kk|v&Y;G{lE`?lt&X>I~;@}QPpYlg0bOK}2B`=&VTg=sYP6ECRO5k8WpF2v z`qf0QL-`ZgJ{T?>1*nl16O;9a->z@2e^dS0eCyd|fEFqPlTAk&T`&* zj`MKU_0dAd5gK%j2OP%@f^y_yJ%+$y5el3nR>Me$0sI_97(u{^MG9dI!9bV*_@0PK zjK>kIF5nDd4d82XkQsiua5!>$duHd{{>a7J?Rv4PT|68c-@dtXV}C4P`?R4pwfy1e zne8h(m-k2KYIo{kQ=2#pEUK!KNYIawp3!Al~E!Nb-i-nI@bVcorlc7A2_Z!U0xBIB}&_61-@47d!R!goPj6A z;svqeQ?*<5_Zv4iKit=*61~`^kjubDQ)O-tc}1ZDlWd+m23X#)0bY5*a#HoOwB`Ou zN90VeS{0kulJj<5 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 0f3c3e12bc6b690a1a8d0eae4193f0b31f761bce..a4739517ad9f9e047186ac7a997ab5bbc7345832 100644 GIT binary patch literal 7554 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 CKk!Nb literal 4265 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 UhRz3Q{}bT^$L)KBXvq}*54hXdQvd(} 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