diff --git a/app/main.py b/app/main.py index e565337..3f54164 100644 --- a/app/main.py +++ b/app/main.py @@ -1,9 +1,13 @@ -from fastapi import APIRouter,FastAPI, Query -from app.auth import send_otp, verify_otp -from app.jwt import create_access_token +from fastapi import FastAPI from app.routers import otp +from app.models.database import init_db app = FastAPI() + +@app.on_event("startup") +def startup(): + init_db() + app.include_router(otp.router) diff --git a/app/models/database.py b/app/models/database.py new file mode 100644 index 0000000..ae0f1ee --- /dev/null +++ b/app/models/database.py @@ -0,0 +1,94 @@ +from sqlalchemy import create_engine, text, select +from sqlalchemy.orm import sessionmaker +import os +from dotenv import load_dotenv + +load_dotenv() + +DATABASE_URL = os.getenv("DATABASE_URL") +if not DATABASE_URL: + raise ValueError("DATABASE_URL environment variable is not set") + +engine = create_engine( + DATABASE_URL, + echo=True, + pool_size=5, + max_overflow=10 +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + 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) + +def test_connection(): + try: + db = SessionLocal() + db.execute(text("SELECT 1")) + db.close() + return True + except Exception as e: + 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() + try: + db_user = UserORM( + phone_number=phone_number, + name=name, + email=email + ) + + db.add(db_user) + db.commit() + db.refresh(db_user) + + # Convert to Pydantic model + user = User( + id=db_user.id, + phone_number=db_user.phone_number, + name=db_user.name, + email=db_user.email + ) + return user + + 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() + try: + stmt = select(UserORM).where(UserORM.phone_number == phone_number) + result = db.execute(stmt) + db_user = result.scalar_one_or_none() + + if db_user: + user = User( + id=db_user.id, + phone_number=db_user.phone_number, + name=db_user.name, + email=db_user.email + ) + return user + return None + + finally: + db.close() diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 0000000..ef7aa63 --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,39 @@ +from sqlalchemy import Column, Integer, String, BigInteger +from sqlalchemy.orm import DeclarativeBase +from pydantic import BaseModel, EmailStr +from typing import Optional + +class Base(DeclarativeBase): + pass + +class UserORM(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + phone_number = Column(String(20), nullable=False, unique=True, index=True) + 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 diff --git a/app/routers/otp.py b/app/routers/otp.py index 1694f17..2499cfe 100644 --- a/app/routers/otp.py +++ b/app/routers/otp.py @@ -1,6 +1,8 @@ from fastapi import APIRouter, Query 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 + router = APIRouter(prefix="/otp", tags=["Otp"]) @router.post("/send-otp") @@ -10,8 +12,63 @@ def send_otp_route(phone: str = Query(...)): @router.post("/verify-otp") def verify_otp_route(phone: str = Query(...), code: str = Query(...)): result = verify_otp(phone, code) + if result["status"] == "approved": - # ✅ OTP verified, generate token - token = create_access_token({"phone": phone}) - return {"status": "approved", "access_token": token} - return result \ No newline at end of file + existing_user = get_user_by_phone(phone) + + if existing_user: + token = create_access_token({"phone": phone, "user_id": existing_user.id}) + return { + "status": "approved", + "access_token": token, + "message": f"Welcome back, {existing_user.name}!", + "user_exists": True, + "user": { + "id": existing_user.id, + "phone_number": existing_user.phone_number, + "name": existing_user.name, + "email": existing_user.email + } + } + else: + return { + "status": "approved", + "message": "OTP verified. Please provide your name and email to complete registration.", + "user_exists": False + } + + return { + "status": result["status"], + "message": result.get("message", "OTP verification failed"), + "user_exists": False + } + +@router.post("/register") +def register_user(phone_number: str, name: str, email: str): + try: + existing_user = get_user_by_phone(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 + ) + + token = create_access_token({"phone": str(phone_number), "user_id": new_user.id}) + + return { + "status": "registered", + "access_token": token, + "message": f"Welcome {new_user.name}! Your account has been created successfully.", + "user": { + "id": new_user.id, + "phone_number": new_user.phone_number, + "name": new_user.name, + "email": new_user.email + } + } + + except Exception as e: + return {"status": "error", "message": f"Failed to register user: {str(e)}"} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3d7d5b2..dabdb15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,9 @@ -fastapi -uvicorn -python-dotenv -twilio -python-jose \ No newline at end of file +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +sqlalchemy==2.0.36 +psycopg[binary]==3.2.9 +python-dotenv==1.0.0 +email-validator==2.1.0 +pydantic==2.5.0 + +twilio==8.10.3 \ No newline at end of file