From 80a1d7d84d35e8979f0aea281e8f504b9df84105 Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:30:06 +0530 Subject: [PATCH 1/8] added db:Session in func params that comes from dependency injection of get_db at routes --- app/models/database.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/app/models/database.py b/app/models/database.py index ae0f1ee..ea8f35d 100644 --- a/app/models/database.py +++ b/app/models/database.py @@ -1,7 +1,11 @@ +from pathlib import Path from sqlalchemy import create_engine, text, select -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, Session import os from dotenv import load_dotenv +from app.models.user import Base +from app.schemas.user_schemas import User +from app.models.user import UserORM load_dotenv() @@ -26,7 +30,6 @@ def get_db(): db.close() def init_db(): - from app.models.user import Base # Drop all tables and recreate them (for development only!) # Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) @@ -41,10 +44,7 @@ def test_connection(): print(f" Database test failed: {e}") return False -def create_user(phone_number: str, name: str, email: str): - from app.models.user import UserORM, User - - db = SessionLocal() +def create_user(db:Session,phone_number: str, name: str, email: str): try: db_user = UserORM( phone_number=phone_number, @@ -68,13 +68,9 @@ def create_user(phone_number: str, name: str, email: str): except Exception as e: db.rollback() raise e - finally: - db.close() -def get_user_by_phone(phone_number: str): - from app.models.user import UserORM, User - - db = SessionLocal() + +def get_user_by_phone(db:Session,phone_number: str): try: stmt = select(UserORM).where(UserORM.phone_number == phone_number) result = db.execute(stmt) @@ -90,5 +86,5 @@ def get_user_by_phone(phone_number: str): return user return None - finally: - db.close() + except Exception as e: + raise e From 6632a0f8d9efe7b5881f085088bc7f6d12cca926 Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:31:12 +0530 Subject: [PATCH 2/8] moved pydantic schemas to app/schemas/user_schemas.py --- app/models/user.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/models/user.py b/app/models/user.py index ef7aa63..1ce875e 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -14,26 +14,4 @@ class UserORM(Base): name = Column(String(255), nullable=False) email = Column(String(255), nullable=False, unique=True) -# Pydantic Models for API validation and data handling -class User(BaseModel): - id: Optional[int] = None - phone_number: str - name: str - email: EmailStr - - class Config: - from_attributes = True # For Pydantic v2 - -class UserCreate(BaseModel): - phone_number: str - name: str - email: EmailStr -class UserResponse(BaseModel): - id: int - phone_number: str - name: str - email: str - - class Config: - from_attributes = True From 27768e182c06161c219cff3d3c3fcab952a85897 Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:32:36 +0530 Subject: [PATCH 3/8] added status_codes, changed query path parameters to json bodies and added db:Session as dependency injection --- app/routers/otp.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/app/routers/otp.py b/app/routers/otp.py index 2499cfe..30c2136 100644 --- a/app/routers/otp.py +++ b/app/routers/otp.py @@ -1,23 +1,26 @@ -from fastapi import APIRouter, Query +from fastapi import APIRouter, status , Depends from app.jwt import create_access_token from app.auth import verify_otp, send_otp -from app.models.database import get_user_by_phone, create_user +from app.models.database import get_user_by_phone, create_user, get_db +from app.schemas.otp_schemas import OTPRequest, OTPVerifyRequest +from app.schemas.user_schemas import UserCreate +from sqlalchemy.orm import Session router = APIRouter(prefix="/otp", tags=["Otp"]) -@router.post("/send-otp") -def send_otp_route(phone: str = Query(...)): - return send_otp(phone) +@router.post("/send-otp", status_code= status.HTTP_202_ACCEPTED) +def send_otp_route(data : OTPRequest): + return send_otp(data.phone) -@router.post("/verify-otp") -def verify_otp_route(phone: str = Query(...), code: str = Query(...)): - result = verify_otp(phone, code) +@router.post("/verify-otp", status_code=status.HTTP_200_OK) +def verify_otp_route(data: OTPVerifyRequest, db: Session = Depends(get_db)): + result = verify_otp(data.phone, data.code) if result["status"] == "approved": - existing_user = get_user_by_phone(phone) + existing_user = get_user_by_phone(db,data.phone) if existing_user: - token = create_access_token({"phone": phone, "user_id": existing_user.id}) + token = create_access_token({"phone": data.phone, "user_id": existing_user.id}) return { "status": "approved", "access_token": token, @@ -43,20 +46,20 @@ def verify_otp_route(phone: str = Query(...), code: str = Query(...)): "user_exists": False } -@router.post("/register") -def register_user(phone_number: str, name: str, email: str): +@router.post("/register",status_code=status.HTTP_201_CREATED) +def register_user(data: UserCreate, db: Session = Depends(get_db)): try: - existing_user = get_user_by_phone(phone_number) + existing_user = get_user_by_phone(db,data.phone_number) if existing_user: return {"status": "error", "message": "User already exists with this phone number"} - new_user = create_user( - phone_number=phone_number, - name=name, - email=email + new_user = create_user(db, + phone_number=data.phone_number, + name=data.name, + email=data.email ) - token = create_access_token({"phone": str(phone_number), "user_id": new_user.id}) + token = create_access_token({"phone": str(data.phone_number), "user_id": new_user.id}) return { "status": "registered", From 2bd4c574b9b26f038bb6083ba718e9b6146615a0 Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:33:59 +0530 Subject: [PATCH 4/8] added otp request and verify schemas --- app/schemas/otp_schemas.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/schemas/otp_schemas.py diff --git a/app/schemas/otp_schemas.py b/app/schemas/otp_schemas.py new file mode 100644 index 0000000..a4bb4c4 --- /dev/null +++ b/app/schemas/otp_schemas.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel, Field + +class OTPRequest(BaseModel): + phone: str = Field(..., example="+91551234567") + +class OTPVerifyRequest(BaseModel): + phone: str = Field(..., example="+91551234567") + code: str = Field(..., example="123456") From cc38f5e2dd1610583e70423c805b2d226734620b Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:34:47 +0530 Subject: [PATCH 5/8] added user specific Pydantic Schemas --- app/schemas/user_schemas.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/schemas/user_schemas.py diff --git a/app/schemas/user_schemas.py b/app/schemas/user_schemas.py new file mode 100644 index 0000000..b9edba9 --- /dev/null +++ b/app/schemas/user_schemas.py @@ -0,0 +1,29 @@ +from typing import Optional + +from pydantic import BaseModel, EmailStr, Field + + +class User(BaseModel): + id: Optional[int] = None + phone_number: str + name: str + email: EmailStr + + class Config: + from_attributes = True + + +class UserResponse(BaseModel): + id: int + phone_number: str + name: str + email: str + + class Config: + from_attributes = True + + +class UserCreate(BaseModel): + phone_number: str = Field(..., example="+91551234567") + name: str = Field(..., example="Alice") + email: EmailStr = Field(..., example="alice@example.com") \ No newline at end of file From 3abc8793a165825983efcae555f2cfe928016fee Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:35:56 +0530 Subject: [PATCH 6/8] changed deprecated v1 services to v2 --- app/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/auth.py b/app/auth.py index 199943a..5beda32 100644 --- a/app/auth.py +++ b/app/auth.py @@ -6,7 +6,7 @@ def send_otp(phone: str): try: - verification = client.verify.services(TWILIO_VERIFY_SERVICE_SID).verifications.create( + verification = client.verify.v2.services(TWILIO_VERIFY_SERVICE_SID).verifications.create( to=phone, channel="sms" ) return {"status": verification.status} @@ -16,7 +16,7 @@ def send_otp(phone: str): def verify_otp(phone: str, code: str): try: - verification_check = client.verify.services(TWILIO_VERIFY_SERVICE_SID).verification_checks.create( + verification_check = client.verify.v2.services(TWILIO_VERIFY_SERVICE_SID).verification_checks.create( to=phone, code=code ) return {"status": verification_check.status} From 8df2a3602eef8f688046de24d1be518866e0a22e Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:37:43 +0530 Subject: [PATCH 7/8] added jwt verify_access_token, get_current_user methods for dependency injections for any future routes --- app/jwt.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/jwt.py b/app/jwt.py index ea72bc2..3186e64 100644 --- a/app/jwt.py +++ b/app/jwt.py @@ -2,17 +2,52 @@ from datetime import datetime, timedelta import os from dotenv import load_dotenv +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from app.models.user import UserORM +from app.models.database import get_db +from sqlalchemy.orm import Session load_dotenv() +security = HTTPBearer() + # Load secret from env SECRET_KEY = os.getenv("JWT_SECRET_KEY") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 # 30 minutes validity + def create_access_token(data: dict): to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) token = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return token + +def verify_access_token(credentials: HTTPAuthorizationCredentials = Depends(security),) -> dict: + token = credentials.credentials + try: + payload = jwt.decode( + token, + SECRET_KEY, + algorithms=[ALGORITHM], + options={"verify_aud": False}, + ) + except JWTError: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials" + ) + + return payload + +def get_current_user(token_data: dict = Depends(verify_access_token), db: Session = Depends(get_db),) -> UserORM: + user_id = token_data.get("user_id") + user = db.query(UserORM).get(user_id) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User not found" + ) + return user \ No newline at end of file From 97d178a277d5bc7efab99a89d978043f14a6dad0 Mon Sep 17 00:00:00 2001 From: kowshikdontu Date: Sun, 27 Jul 2025 14:39:14 +0530 Subject: [PATCH 8/8] added __init__.py for python to treat each folder as package --- app/__init__.py | 0 app/models/__init__.py | 0 app/routers/__init__.py | 0 app/schemas/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/models/__init__.py create mode 100644 app/routers/__init__.py create mode 100644 app/schemas/__init__.py diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/routers/__init__.py b/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29