Girish Jeswani commited on
Commit
f052ff5
·
1 Parent(s): 0ba69db

add database code

Browse files
multi_llm_chatbot_backend/app/core/auth.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime, timedelta
3
+ from typing import Optional
4
+ from fastapi import HTTPException, Depends, status
5
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6
+ from passlib.context import CryptContext
7
+ from jose import JWTError, jwt
8
+ from bson import ObjectId
9
+ from app.core.database import get_database
10
+ from app.models.user import User, UserResponse
11
+
12
+ # Password hashing
13
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
14
+
15
+ # JWT settings
16
+ SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-this-in-production")
17
+ ALGORITHM = "HS256"
18
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30 * 24 * 60 # 30 days
19
+
20
+ # Security scheme
21
+ security = HTTPBearer()
22
+
23
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
24
+ """Verify a password against its hash"""
25
+ return pwd_context.verify(plain_password, hashed_password)
26
+
27
+ def get_password_hash(password: str) -> str:
28
+ """Hash a password"""
29
+ return pwd_context.hash(password)
30
+
31
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
32
+ """Create a JWT access token"""
33
+ to_encode = data.copy()
34
+ if expires_delta:
35
+ expire = datetime.utcnow() + expires_delta
36
+ else:
37
+ expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
38
+
39
+ to_encode.update({"exp": expire})
40
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
41
+ return encoded_jwt
42
+
43
+ async def get_user_by_email(email: str) -> Optional[User]:
44
+ """Get user by email from database"""
45
+ db = get_database()
46
+ user_data = await db.users.find_one({"email": email})
47
+ if user_data:
48
+ return User(**user_data)
49
+ return None
50
+
51
+ async def get_user_by_id(user_id: str) -> Optional[User]:
52
+ """Get user by ID from database"""
53
+ try:
54
+ db = get_database()
55
+ user_data = await db.users.find_one({"_id": ObjectId(user_id)})
56
+ if user_data:
57
+ return User(**user_data)
58
+ return None
59
+ except Exception:
60
+ return None
61
+
62
+ async def authenticate_user(email: str, password: str) -> Optional[User]:
63
+ """Authenticate user with email and password"""
64
+ user = await get_user_by_email(email)
65
+ if not user:
66
+ return None
67
+ if not verify_password(password, user.hashed_password):
68
+ return None
69
+ return user
70
+
71
+ async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> User:
72
+ """Get current user from JWT token"""
73
+ credentials_exception = HTTPException(
74
+ status_code=status.HTTP_401_UNAUTHORIZED,
75
+ detail="Could not validate credentials",
76
+ headers={"WWW-Authenticate": "Bearer"},
77
+ )
78
+
79
+ try:
80
+ payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
81
+ user_id: str = payload.get("sub")
82
+ if user_id is None:
83
+ raise credentials_exception
84
+ except JWTError:
85
+ raise credentials_exception
86
+
87
+ user = await get_user_by_id(user_id)
88
+ if user is None:
89
+ raise credentials_exception
90
+
91
+ if not user.is_active:
92
+ raise HTTPException(
93
+ status_code=status.HTTP_401_UNAUTHORIZED,
94
+ detail="Inactive user"
95
+ )
96
+
97
+ return user
98
+
99
+ async def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
100
+ """Get current active user"""
101
+ if not current_user.is_active:
102
+ raise HTTPException(status_code=400, detail="Inactive user")
103
+ return current_user
104
+
105
+ def create_user_response(user: User) -> UserResponse:
106
+ """Create user response model"""
107
+ return UserResponse(
108
+ id=str(user.id),
109
+ firstName=user.firstName,
110
+ lastName=user.lastName,
111
+ email=user.email,
112
+ academicStage=user.academicStage,
113
+ researchArea=user.researchArea,
114
+ created_at=user.created_at,
115
+ last_login=user.last_login
116
+ )
multi_llm_chatbot_backend/app/core/database.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from motor.motor_asyncio import AsyncIOMotorClient
3
+ from pymongo.errors import ConnectionFailure
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class Database:
9
+ client: AsyncIOMotorClient = None
10
+ database = None
11
+
12
+ db = Database()
13
+
14
+ async def connect_to_mongo():
15
+ """Create database connection"""
16
+ try:
17
+ # Get MongoDB connection string from environment
18
+ mongo_url = os.getenv("MONGODB_CONNECTION_STRING")
19
+ if not mongo_url:
20
+ raise ValueError("MONGODB_CONNECTION_STRING environment variable not set")
21
+
22
+ # Get database name from environment or use default
23
+ db_name = os.getenv("MONGODB_DATABASE_NAME", "phd_advisor")
24
+
25
+ db.client = AsyncIOMotorClient(mongo_url)
26
+ db.database = db.client[db_name]
27
+
28
+ # Test connection
29
+ await db.client.admin.command('ping')
30
+ logger.info(f"Successfully connected to MongoDB database: {db_name}")
31
+
32
+ # Create indexes for better performance
33
+ await create_indexes()
34
+
35
+ except ConnectionFailure as e:
36
+ logger.error(f"Failed to connect to MongoDB: {e}")
37
+ raise
38
+ except Exception as e:
39
+ logger.error(f"Unexpected error connecting to MongoDB: {e}")
40
+ raise
41
+
42
+ async def close_mongo_connection():
43
+ """Close database connection"""
44
+ if db.client:
45
+ db.client.close()
46
+ logger.info("Disconnected from MongoDB")
47
+
48
+ async def create_indexes():
49
+ """Create database indexes for performance"""
50
+ try:
51
+ # Index for users collection
52
+ await db.database.users.create_index("email", unique=True)
53
+ await db.database.users.create_index("created_at")
54
+
55
+ # Indexes for chat_sessions collection
56
+ await db.database.chat_sessions.create_index("user_id")
57
+ await db.database.chat_sessions.create_index("created_at")
58
+ await db.database.chat_sessions.create_index([("user_id", 1), ("created_at", -1)])
59
+
60
+ logger.info("Database indexes created successfully")
61
+ except Exception as e:
62
+ logger.warning(f"Error creating indexes: {e}")
63
+
64
+ def get_database():
65
+ """Get database instance"""
66
+ return db.database
multi_llm_chatbot_backend/app/models/user.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr, Field, ConfigDict
2
+ from typing import Optional, List, Any
3
+ from datetime import datetime
4
+ from bson import ObjectId
5
+
6
+ class PyObjectId(ObjectId):
7
+ @classmethod
8
+ def __get_validators__(cls):
9
+ yield cls.validate
10
+
11
+ @classmethod
12
+ def validate(cls, v, handler=None):
13
+ if isinstance(v, ObjectId):
14
+ return v
15
+ if isinstance(v, str):
16
+ if ObjectId.is_valid(v):
17
+ return ObjectId(v)
18
+ raise ValueError("Invalid ObjectId")
19
+
20
+ @classmethod
21
+ def __get_pydantic_json_schema__(cls, field_schema):
22
+ field_schema.update(type="string")
23
+
24
+ class UserCreate(BaseModel):
25
+ firstName: str
26
+ lastName: str
27
+ email: EmailStr
28
+ password: str
29
+ academicStage: Optional[str] = None
30
+ researchArea: Optional[str] = None
31
+
32
+ class UserLogin(BaseModel):
33
+ email: EmailStr
34
+ password: str
35
+
36
+ class User(BaseModel):
37
+ model_config = ConfigDict(
38
+ populate_by_name=True,
39
+ arbitrary_types_allowed=True,
40
+ json_encoders={ObjectId: str}
41
+ )
42
+
43
+ id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
44
+ firstName: str
45
+ lastName: str
46
+ email: EmailStr
47
+ hashed_password: str
48
+ academicStage: Optional[str] = None
49
+ researchArea: Optional[str] = None
50
+ created_at: datetime = Field(default_factory=datetime.utcnow)
51
+ last_login: Optional[datetime] = None
52
+ is_active: bool = True
53
+
54
+ class UserResponse(BaseModel):
55
+ id: str
56
+ firstName: str
57
+ lastName: str
58
+ email: str
59
+ academicStage: Optional[str] = None
60
+ researchArea: Optional[str] = None
61
+ created_at: datetime
62
+ last_login: Optional[datetime] = None
63
+
64
+ class ChatSession(BaseModel):
65
+ model_config = ConfigDict(
66
+ populate_by_name=True,
67
+ arbitrary_types_allowed=True,
68
+ json_encoders={ObjectId: str}
69
+ )
70
+
71
+ id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
72
+ user_id: PyObjectId
73
+ title: str
74
+ messages: List[dict] = []
75
+ created_at: datetime = Field(default_factory=datetime.utcnow)
76
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
77
+ is_active: bool = True
78
+
79
+ class ChatSessionResponse(BaseModel):
80
+ id: str
81
+ title: str
82
+ created_at: datetime
83
+ updated_at: datetime
84
+ message_count: int
85
+
86
+ class Token(BaseModel):
87
+ access_token: str
88
+ token_type: str
89
+ user: UserResponse