Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -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)


94 changes: 94 additions & 0 deletions app/models/database.py
Original file line number Diff line number Diff line change
@@ -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()
39 changes: 39 additions & 0 deletions app/models/user.py
Original file line number Diff line number Diff line change
@@ -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
65 changes: 61 additions & 4 deletions app/routers/otp.py
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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
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)}"}
14 changes: 9 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
fastapi
uvicorn
python-dotenv
twilio
python-jose
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