from fastapi import APIRouter, Depends, HTTPException, status, Request, Query, Body from sqlalchemy.orm import Session from typing import List, Optional from app.core.deps import get_db, require_current_user, get_current_user_token from app.core.security import verify_password, get_password_hash, create_access_token from app.auth.models import AppUser, Role from app.auth.schemas import ( LoginRequest, SessionResponse, SessionItem, RegistrationRequest, SendEmailRequest, EmailExistsRequest, UserResponse ) router = APIRouter(prefix="/token", tags=["Authentication & User Session"]) @router.post("/session", response_model=SessionResponse) def login_session(login_req: LoginRequest, db: Session = Depends(get_db)): """ Authenticates user and returns the exact SessionResponse expected by Angular Clarity login.service.ts. """ user = db.query(AppUser).filter(AppUser.email == login_req.email).first() if not user or not verify_password(login_req.password, user.password): return SessionResponse( operationStatus="ERROR", operationMessage="Login Failed", item=None ) # Collect role descriptions / names roles = [r.description or r.name for r in user.roles] if user.roles else ["Admin"] # Generate JWT token = create_access_token( subject=user.user_id, extra_claims={ "email": user.email, "username": user.username, "roles": roles, "user_grp": user.user_grp_id } ) session_item = SessionItem( userId=user.user_id, email=user.email, fullname=user.full_name or user.username, firstName=user.first_name or user.full_name or user.username, username=user.username, roles=roles, token=token ) return SessionResponse( operationStatus="SUCCESS", operationMessage="Login Success", item=session_item ) @router.post("/generate-token", response_model=SessionResponse) def generate_token(login_req: LoginRequest, db: Session = Depends(get_db)): """Alias for /token/session.""" return login_session(login_req, db) @router.get("/logout") @router.post("/logout") def logout_user(): """Logs out the user.""" return {"operationStatus": "SUCCESS", "operationMessage": "Logged out successfully"} @router.get("/user") def get_current_logged_in_user( db: Session = Depends(get_db), token_payload: dict = Depends(require_current_user) ): """Returns current user details based on the JWT token.""" user_id = token_payload.get("user_id") or token_payload.get("sub") user = db.query(AppUser).filter(AppUser.user_id == user_id).first() if not user: raise HTTPException(status_code=404, detail="User not found") roles = [r.description or r.name for r in user.roles] if user.roles else ["Admin"] return { "userId": user.user_id, "email": user.email, "username": user.username, "fullname": user.full_name, "roles": roles, "status": user.status } @router.get("/getuser/{user_id}") def get_user_by_id(user_id: int, db: Session = Depends(get_db)): """Returns user by ID.""" user = db.query(AppUser).filter(AppUser.user_id == user_id).first() if not user: raise HTTPException(status_code=404, detail="User not found") roles = [r.description or r.name for r in user.roles] if user.roles else ["Admin"] return { "userId": user.user_id, "email": user.email, "username": user.username, "fullname": user.full_name, "roles": roles } @router.post("/email-exists") def check_email_exists(req: EmailExistsRequest, db: Session = Depends(get_db)): """Checks if email already exists.""" user = db.query(AppUser).filter(AppUser.email == req.email).first() return {"exists": user is not None} @router.post("/users/sysaccount/savesysaccount") @router.post("/savesysaccount") def save_sys_account(data: dict = Body(...)): """ Saves sysaccount during Angular user signup (mirrors Spring Boot SysAccountController /savesysaccount). """ account_id = data.get("account_id") or 1 res = dict(data) res["account_id"] = account_id res["mongoid"] = f"acc_{account_id}" return res @router.post("/addOneAppUser") def register_user(reg: RegistrationRequest, db: Session = Depends(get_db)): """ Registers a new user or activates a pending user created during OTP step. Mirrors Spring Boot SessionController /addOneAppUser. """ email = reg.email.strip() user = db.query(AppUser).filter(AppUser.email == email).first() raw_pwd = reg.new_password or reg.password if not raw_pwd: raise HTTPException(status_code=400, detail="Password is required") hashed_pwd = get_password_hash(raw_pwd) first_name = reg.first_name or reg.fullName or email.split("@")[0] last_name = reg.last_name or "" full_name = reg.fullName or f"{first_name} {last_name}".strip() username = reg.username or email.split("@")[0] if user: if user.status == "ACTIVE" and user.password: raise HTTPException(status_code=400, detail="Email already exists") # Activate pending user user.password = hashed_pwd user.first_name = first_name user.last_name = last_name user.full_name = full_name user.username = username user.status = "ACTIVE" user.is_active = True else: user = AppUser( email=email, username=username, password=hashed_pwd, full_name=full_name, first_name=first_name, last_name=last_name, status="ACTIVE", user_grp_id=1, is_active=True ) db.add(user) # Assign Admin role admin_role = db.query(Role).filter(Role.name == "ROLE_ADMIN").first() if admin_role and admin_role not in user.roles: user.roles.append(admin_role) db.commit() db.refresh(user) return { "userId": user.user_id, "email": user.email, "username": user.username, "fullName": user.full_name, "firstName": user.first_name, "roles": [r.description or r.name for r in user.roles], "message": "User registered successfully" } @router.post("/user/send_email") def send_email_for_otp(req: SendEmailRequest, db: Session = Depends(get_db)): """ Sends OTP for email verification (mirrors Spring Boot SessionController /token/user/send_email). """ import random from app.core.email import email_service email = req.email.strip() user = db.query(AppUser).filter(AppUser.email == email).first() if user and user.status == "ACTIVE" and user.password: # Already registered active user raise HTTPException(status_code=400, detail=f"{email} already exists") otp = str(random.randint(100000, 999999)) if not user: username = req.username or email.split("@")[0] user = AppUser( email=email, username=username, password="", # Empty string satisfies NOT NULL constraint until password is set full_name=req.fullName or username, first_name=req.fullName or username, status="PENDING", random_no=otp, is_active=False ) db.add(user) else: user.random_no = otp db.commit() email_service.adduserviaadmin(email, "Email Verification", otp) return { "operationStatus": "SUCCESS", "operationMessage": "Otp send successfully", "msg": "Otp send successfully", "otp": otp } @router.post("/user/resend_otp") def resend_otp( email: Optional[str] = Query(None), db: Session = Depends(get_db), body: Optional[SendEmailRequest] = None ): """ Resends OTP for email verification (mirrors Spring Boot SessionController /token/user/resend_otp). Supports query parameter (?email=...) or JSON body. """ import random from app.core.email import email_service target_email = email or (body.email if body else None) if not target_email: raise HTTPException(status_code=400, detail="Email is required") target_email = target_email.strip() user = db.query(AppUser).filter(AppUser.email == target_email).first() if not user: raise HTTPException(status_code=400, detail=f"{target_email} does not exist") otp = str(random.randint(100000, 999999)) user.random_no = otp db.commit() email_service.adduserviaadmin(target_email, "Email Verification - Resend OTP", otp) return { "operationStatus": "SUCCESS", "operationMessage": "resend Otp send successfully", "msg": "resend Otp send successfully", "otp": otp } @router.post("/user/otp_verification") def otp_verification( email: str = Query(...), otp: str = Query(...), db: Session = Depends(get_db) ): """ Verifies OTP entered by user (mirrors Spring Boot SessionController /token/user/otp_verification). Query parameters: ?email=...&otp=... """ email_clean = email.replace(" ", "+").strip() user = db.query(AppUser).filter(AppUser.email == email_clean).first() if not user: raise HTTPException(status_code=400, detail="User not found") if user.random_no and user.random_no.strip() == otp.strip(): return { "operationStatus": "SUCCESS", "operationMessage": "OTP Verified", "msg": "OTP Verified" } else: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Wrong OTP" )