import os from fastapi import FastAPI, Depends, HTTPException , Depends , status from sqlalchemy.orm import Session import os import sys from pathlib import Path import json # ensure project root (agent/) is on sys.path so sibling packages like "s3" can be imported project_root = Path(__file__).resolve().parents[2] # -> ...\openai_agents\agent if str(project_root) not in sys.path: sys.path.insert(0, str(project_root)) from passlib.context import CryptContext from dotenv import load_dotenv from sqlalchemy import text # from . import database, models, crud # import database , models , crud from .schemas import ( UserCreate, UserResponse, ConversationCreate, ConversationResponse, OrganizationCreate, OrganizationResponse, PlanCreate, PlanResponse, LoginRequest, DatasetBase, DatasetCreate, DatasetUpdate, DatasetResponse, UserDatasetsMetadataBase, UserDatasetsMetadataCreate, UserDatasetsMetadataResponse, OrganizationBase, OrganizationCreate, OrganizationUpdate, OrganizationResponse ) from .database import create_tables, get_db, engine from . import crud, models from uuid import UUID from fastapi.security import OAuth2PasswordRequestForm from datetime import timedelta, datetime, timezone from . import auth from .decorators import require_auth from fastapi import APIRouter load_dotenv() # login_apis_router = FastAPI(title="MyApp", version="1.0.0") login_apis_router = APIRouter(prefix="/auth", tags=["Authentication"]) # Qdrant_router = APIRouter(prefix="/qdrant", tags=["Qdrant_Collections"]) pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # @login_apis_router.on_event("startup") # def on_startup(): # create_tables() # print("Database tables created/checked") @login_apis_router.get("/") def health(): return {"message": "OK"} @login_apis_router.post("/users/create") def create_user_endpoint(user: UserCreate, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): if crud.get_user_by_username(db, user.username) or crud.get_user_by_email(db, user.email): raise HTTPException(status_code=400, detail="username or email already exists") hashed = pwd_context.hash(user.password) created = crud.create_user(db, user, hashed) return created @login_apis_router.get("/users") def list_users(db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): print("Listing all users") users = db.query(models.User).all() # print("all users retrieved",users) return {"users": users, "count": len(users)} @login_apis_router.get("/users/{username}") def get_user_by_username(username: str, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): user = crud.get_user_by_username(db, username) print("get user by username api hitted") if not user: raise HTTPException(status_code=404, detail=f"User '{username}' not found") return {"user": user} # 🔹 Get user by ID (UUID) @login_apis_router.get("/users/id/{user_id}") def get_user_by_id(user_id: UUID, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): user = db.query(models.User).filter(models.User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail=f"User with id '{user_id}' not found") return {"user": user} # Login - returns JWT token @login_apis_router.post("/login") def login(login_data: LoginRequest, db: Session = Depends(get_db)): # Find user by email user = db.query(models.User).filter(models.User.email == login_data.email).first() if not user or not auth.verify_password(login_data.password, user.password): raise HTTPException(status_code=401, detail="Invalid email or password") # Update last login time user.last_login = datetime.now(timezone.utc) db.commit() db.refresh(user) # Create JWT token access_token_expires = timedelta(minutes=auth.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = auth.create_access_token( data={"sub": user.email}, expires_delta=access_token_expires ) # Return token and complete user data return { "message": "User Logged in Successfully", "access_token": access_token, "token_type": "bearer", "user": { "id": str(user.id), "fullname": user.fullname, "username": user.username, "email": user.email, "role": user.role, "is_active": user.is_active, "last_login": user.last_login.isoformat() if user.last_login else None, "organization_id": str(user.organization_id) if user.organization_id else None, "provider_id": str(user.provider_id) if user.provider_id else None, "created_at": user.created_at.isoformat() if user.created_at else None, "updated_at": user.updated_at.isoformat() if user.updated_at else None, } } # 🚪 Logout - JWT is stateless, so simulate logout @login_apis_router.post("/logout") def logout(): return {"message": "User logged out successfully"} # @login_apis_router.post("/conversations", status_code=201) # async def create_conversation(payload: dict, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): # """ # Create a new conversation entry using raw SQL # """ # try: # user_id = payload.get("user_id") # convo_id = payload.get("convo_id") # data = payload.get("data", {}) # is_saved = payload.get("is_saved", False) # # Convert data fields to JSON # user_query = json.dumps(data.get("user_query", {})) # response = json.dumps(data.get("response", {})) # # Extract file_metadata (if present inside response.artifacts) # artifacts = data.get("response", {}).get("artifacts", []) # file_metadata = json.dumps(artifacts) if artifacts else json.dumps([]) # created_at = datetime.now(timezone.utc) # updated_at = created_at # insert_query = text(""" # INSERT INTO conversation_data (user_id, convo_id, user_query, response, file_metadata, is_saved,created_at, updated_at) # VALUES (:user_id, :convo_id, :user_query, :response, :file_metadata, :is_saved, :created_at, :updated_at) # RETURNING id, created_at, updat # """) # result = db.execute(insert_query, { # "user_id": user_id, # "convo_id": convo_id, # "user_query": user_query, # "response": response, # "file_metadata": file_metadata, # "is_saved": is_saved, # "created_at": created_at, # "updated_at": updated_at # }) # db.commit() # inserted = result.fetchone() # return { # "message": "Conversation created successfully", # "id": str(inserted[0]), # "convo_id": convo_id, # "created_at": str(inserted[1]), # "is_saved": is_saved # } # except Exception as e: # db.rollback() # raise HTTPException(status_code=500, detail=f"Error creating conversation: {str(e)}") # @login_apis_router.post("/conversations", status_code=201) # async def create_conversation(payload: dict, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): # """ # Create a new conversation entry using raw SQL # """ # try: # user_id = payload.get("user_id") # convo_id = payload.get("convo_id") # data = payload.get("data", {}) # is_saved = payload.get("is_saved", False) # # Convert data fields to JSON # user_query = json.dumps(data.get("user_query", {})) # response = json.dumps(data.get("response", {})) # # Extract file_metadata (if present inside response.artifacts) # artifacts = data.get("response", {}).get("artifacts", []) # file_metadata = json.dumps(artifacts) if artifacts else json.dumps([]) # created_at = datetime.now(timezone.utc) # updated_at = created_at # insert_query = text(""" # INSERT INTO conversation_data ( # user_id, convo_id, user_query, response, file_metadata, is_saved, created_at, updated_at # ) # VALUES ( # :user_id, :convo_id, :user_query, :response, :file_metadata, :is_saved, :created_at, :updated_at # ) # RETURNING id, created_at # """) # result = db.execute(insert_query, { # "user_id": user_id, # "convo_id": convo_id, # "user_query": user_query, # "response": response, # "file_metadata": file_metadata, # "is_saved": is_saved, # "created_at": created_at, # "updated_at": updated_at # }) # db.commit() # inserted = result.fetchone() # return { # "message": "Conversation created successfully", # "id": str(inserted[0]), # "convo_id": convo_id, # "created_at": str(inserted[1]), # "is_saved": is_saved # } # except Exception as e: # db.rollback() # raise HTTPException(status_code=500, detail=f"Error creating conversation: {str(e)}") @login_apis_router.delete("/reset-db") def reset_database(): with engine.connect() as conn: conn.execute(text("DROP SCHEMA public CASCADE;")) conn.execute(text("CREATE SCHEMA public;")) conn.commit() return {"message": " All tables dropped and schema reset successfully"} # ----------------------------- # ORGANIZATION ROUTES # ----------------------------- @login_apis_router.post("/Createorganizations", status_code=201) def create_organization(payload: OrganizationCreate, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): org = models.Organization( name=payload.name, domain=payload.domain, plan_id=payload.plan_id, location=payload.location, city=payload.city, state=payload.state, country=payload.country, is_active=payload.is_active if payload.is_active is not None else True, ) db.add(org) db.commit() db.refresh(org) return {"organization": org} @login_apis_router.get("/organizations") def list_organizations(db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): print("organization api hitted") organizations=db.query(models.Organization).all() # print("organization data",query) return {"organizations": organizations, "count": len(organizations)} @login_apis_router.get("/organizations/{organization_id}") def get_organization(organization_id: UUID, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): org = db.query(models.Organization).filter(models.Organization.id == organization_id).first() if not org: raise HTTPException(status_code=404, detail=f"Organization with id '{organization_id}' not found") return {"organization": org} # ----------------------------- # DATASET ROUTES # ----------------------------- @login_apis_router.get("/getuserdatasets/{user_id}") def get_datasets_by_user(user_id: UUID, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): datasets = db.query(models.Dataset).filter(models.Dataset.created_by == user_id).all() if not datasets: raise HTTPException(status_code=404, detail=f"No datasets found for user ID {user_id}") return {"datasets": datasets, "count": len(datasets)} # ----------------------------- # PLAN ROUTES # ----------------------------- @login_apis_router.post("/Createplans", response_model=PlanResponse, status_code=201) def create_plan(payload: PlanCreate, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): plan = models.Plan( plan_type=payload.plan_type, price=payload.price, features=payload.features, ) db.add(plan) db.commit() db.refresh(plan) return plan @login_apis_router.get("/plans", response_model=list[PlanResponse]) def list_plans(db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): return db.query(models.Plan).all() @login_apis_router.get("/plans/{plan_id}", response_model=PlanResponse) def get_plan(plan_id: UUID, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): plan = db.query(models.Plan).filter(models.Plan.id == plan_id).first() if not plan: raise HTTPException(status_code=404, detail=f"Plan with id '{plan_id}' not found") return plan # ----------------------------- # USERMETADATA ROUTES # ----------------------------- @login_apis_router.post("/UserMetadataCreate", response_model=UserDatasetsMetadataResponse) def create_metadata(metadata_in: UserDatasetsMetadataCreate, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): return crud.create_user_dataset_metadata(db=db, metadata_in=metadata_in) @login_apis_router.get("/UserMetadata/{user_id}", response_model=list[UserDatasetsMetadataResponse]) def get_metadata(user_id: str, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth)): result = crud.get_user_dataset_metadata(db=db, user_id=user_id) if not result: raise HTTPException(status_code=404, detail="No metadata found for this user.") return result # ----------------------------- # ORGANIZATIONS ROUTES # ----------------------------- @login_apis_router.post("/createorganization") def create_organization(payload: OrganizationCreate, db: Session = Depends(get_db)): """ API to create an organization. Only users with role='superadmin' can create an organization. """ # Step 1: Fetch user from User table using user_id from payload user = db.query(models.User).filter(models.User.id == payload.created_by).first() if not user: raise HTTPException(status_code=404, detail="User not found") # Step 2: Validate user role if user.role.lower() != "superadmin": raise HTTPException(status_code=403, detail="Only Superadmin can create an organization") # Step 3: Create new organization entry new_org = models.Organization( name=payload.name, domain=payload.domain, plan_id=payload.plan_id, location=payload.location, city=payload.city, state=payload.state, country=payload.country, is_active=payload.is_active, created_by=payload.created_by, created_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) db.add(new_org) db.commit() db.refresh(new_org) return {"message": "Organization created successfully", "organization": new_org} @login_apis_router.get("/getorganizations", response_model=list[OrganizationResponse]) def get_all_organizations( db: Session = Depends(get_db), auth_data: dict = Depends(require_auth) ): """Fetch all active organizations.""" orgs = db.query(models.Organization).filter(models.Organization.deleted_at.is_(None)).all() return orgs @login_apis_router.get("/getorganization/{org_id}", response_model=OrganizationResponse) def get_organization_by_id( org_id: UUID, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth) ): """Fetch a specific organization by ID.""" org = db.query(models.Organization).filter( models.Organization.id == org_id, models.Organization.deleted_at.is_(None) ).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") return org @login_apis_router.put("/updateorganization/{org_id}", response_model=OrganizationResponse) def update_organization( org_id: UUID, org_data: OrganizationCreate, # still using this schema to receive update data db: Session = Depends(get_db), auth_data: dict = Depends(require_auth) ): """ Update organization details. Only Superadmin users can update organization details. """ # Step 1: Verify user exists user = db.query(models.User).filter(models.User.id == org_data.created_by).first() if not user: raise HTTPException(status_code=404, detail="User not found") # Step 2: Verify role = superadmin if user.role.lower() != "superadmin": raise HTTPException(status_code=403, detail="Only Superadmin can update organization details") # Step 3: Check organization exists org = db.query(models.Organization).filter( models.Organization.id == org_id, models.Organization.deleted_at.is_(None) ).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") # Step 4: Update organization fields update_data = org_data.dict(exclude_unset=True) for key, value in update_data.items(): setattr(org, key, value) org.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(org) return org @login_apis_router.delete("/deleteorganization/{org_id}") def delete_organization( org_id: UUID, db: Session = Depends(get_db), auth_data: dict = Depends(require_auth) ): """ Soft delete an organization. Only Superadmin users can perform this action. """ # Step 1: Extract user_id from token user_id = auth_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="User not authenticated") # Step 2: Fetch user from DB user = db.query(models.User).filter(models.User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="User not found") # Step 3: Check if user is Superadmin if user.role.lower() != "superadmin": raise HTTPException(status_code=403, detail="Only Superadmin can delete organizations") # Step 4: Find organization org = db.query(models.Organization).filter( models.Organization.id == org_id, models.Organization.deleted_at.is_(None) ).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") # Step 5: Soft delete (mark deleted_at timestamp) org.deleted_at = datetime.now(timezone.utc) db.commit() return {"message": f"Organization '{org.name}' deleted successfully"}