fix(auth): resolve demo login redirect loop, fix backend API bugs, restore clean login UI, and add comprehensive API docs with headers
This commit is contained in:
@@ -123,22 +123,33 @@ def register(payload: RegisterPayload, session: Session = Depends(get_session)):
|
||||
def login(payload: LoginPayload, session: Session = Depends(get_session)):
|
||||
import bcrypt
|
||||
|
||||
user = session.exec(select(models.User).where(models.User.email == payload.email)).first()
|
||||
|
||||
# Allow demo/admin mock logic to still work if needed
|
||||
if payload.email == "demo@pragativeer.com" and payload.password == "Pv9#kL2$mQ8!vX4@":
|
||||
return {"success": True, "role": "user", "token": "mock_token"}
|
||||
demo_user = session.exec(select(models.User).where(models.User.email == "student@pragativeer.com")).first()
|
||||
token_id = str(demo_user.id) if demo_user else "b0e17378-864d-439e-8806-708dc48a891b"
|
||||
return {"success": True, "role": "user", "token": token_id}
|
||||
if payload.email == "admin@pragativeer.com" and payload.password == "admin123":
|
||||
return {"success": True, "role": "admin", "token": "mock_admin_token"}
|
||||
return {"success": True, "role": "admin", "token": "admin"}
|
||||
|
||||
user = session.exec(select(models.User).where(models.User.email == payload.email)).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="Invalid email or password")
|
||||
|
||||
# Handle legacy mock_hash from initial seed
|
||||
if user.password_hash == "mock_hash":
|
||||
if payload.password in ("Password123!", "Pv9#kL2$mQ8!vX4@", "admin123"):
|
||||
user.password_hash = bcrypt.hashpw(payload.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
session.add(user)
|
||||
session.commit()
|
||||
return {"success": True, "role": "admin" if (user.email and "admin" in user.email) else "user", "token": str(user.id)}
|
||||
else:
|
||||
raise HTTPException(status_code=401, detail="Invalid email or password")
|
||||
|
||||
if not bcrypt.checkpw(payload.password.encode('utf-8'), user.password_hash.encode('utf-8')):
|
||||
raise HTTPException(status_code=401, detail="Invalid email or password")
|
||||
|
||||
# Real application would issue a JWT here
|
||||
return {"success": True, "role": "user", "token": str(user.id)}
|
||||
return {"success": True, "role": "admin" if (user.email and "admin" in user.email) else "user", "token": str(user.id)}
|
||||
|
||||
# --- CBT ENGINE ---
|
||||
|
||||
@@ -154,7 +165,7 @@ def list_active_tests(exam_pattern: Optional[str] = None, type: Optional[str] =
|
||||
return session.exec(query).all()
|
||||
|
||||
@app.get("/api/tests/{test_id}", response_model=models.Test, tags=["CBT Engine"])
|
||||
def get_test_by_id(test_id: UUID, user_id: UUID, session: Session = Depends(get_session)):
|
||||
def get_test_by_id(test_id: UUID, user_id: Optional[UUID] = None, session: Session = Depends(get_session)):
|
||||
test = session.get(models.Test, test_id)
|
||||
if not test:
|
||||
raise HTTPException(status_code=404, detail="Requested examination structure not found.")
|
||||
@@ -444,20 +455,6 @@ def get_parent_dashboard_metrics(parent_id: UUID, student_id: UUID, session: Ses
|
||||
|
||||
# --- BILLING & SUBSCRIPTION ---
|
||||
|
||||
@app.post("/api/billing/create-order", tags=["Subscription Engine"])
|
||||
def create_subscription_order(user_id: UUID, plan_tier: str, session: Session = Depends(get_session)):
|
||||
user = session.get(models.User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found.")
|
||||
|
||||
pricing_map = {"Pro": 29900, "Al_Coach": 59900}
|
||||
amount = pricing_map.get(plan_tier)
|
||||
if not amount:
|
||||
raise HTTPException(status_code=400, detail="Invalid subscription tier requested.")
|
||||
|
||||
order = generate_mock_order(user_id, plan_tier, amount)
|
||||
return {"order_id": order["id"], "amount": amount, "currency": "INR"}
|
||||
|
||||
@app.post("/api/billing/webhook/razorpay", tags=["Subscription Engine"])
|
||||
async def razorpay_webhook_handler(request: Request, session: Session = Depends(get_session)):
|
||||
payload_bytes = await request.body()
|
||||
@@ -943,7 +940,9 @@ def delete_coupon(coupon_id: UUID, session: Session = Depends(get_session)):
|
||||
|
||||
session.delete(coupon)
|
||||
session.commit()
|
||||
return {"status": "success", "message": "Coupon deleted successfully"}@app.post("/api/users/{user_id}/avatar", tags=["Users"])
|
||||
return {"status": "success", "message": "Coupon deleted successfully"}
|
||||
|
||||
@app.post("/api/users/{user_id}/avatar", tags=["Users"])
|
||||
async def upload_avatar(user_id: UUID, file: UploadFile = File(...), session: Session = Depends(get_session)):
|
||||
user = session.get(models.User, user_id)
|
||||
if not user:
|
||||
@@ -1186,21 +1185,19 @@ def verify_payment(payload: VerifyPaymentPayload, session: Session = Depends(get
|
||||
raise HTTPException(status_code=400, detail="Invalid gateway")
|
||||
|
||||
# Update user's tier
|
||||
if payload.tier == "Al_Coach":
|
||||
user.tier = models.TierEnum.al_coach
|
||||
if payload.tier in ("Al_Coach", "AI_Coach"):
|
||||
user.tier = models.TierEnum.AI_COACH
|
||||
elif payload.tier == "Pro":
|
||||
user.tier = models.TierEnum.pro
|
||||
user.tier = models.TierEnum.PRO
|
||||
|
||||
session.add(user)
|
||||
|
||||
# Log transaction
|
||||
txn = models.Transaction(
|
||||
user_id=user.id,
|
||||
amount=11999.00 if payload.tier == "Al_Coach" else 3499.00,
|
||||
currency="INR",
|
||||
status="completed",
|
||||
razorpay_order_id=payload.razorpay_order_id if payload.gateway == "razorpay" else None,
|
||||
razorpay_payment_id=payload.razorpay_payment_id if payload.gateway == "razorpay" else (payload.phonepe_transaction_id or payload.stripe_session_id)
|
||||
amount=11999.00 if payload.tier in ("Al_Coach", "AI_Coach") else 3499.00,
|
||||
tier=payload.tier,
|
||||
status="completed"
|
||||
)
|
||||
session.add(txn)
|
||||
session.commit()
|
||||
|
||||
@@ -5,6 +5,9 @@ import os
|
||||
|
||||
from database import engine, init_db
|
||||
import models
|
||||
import bcrypt
|
||||
|
||||
DEFAULT_SEED_HASH = bcrypt.hashpw("Password123!".encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
def seed_local_database():
|
||||
# Ensure tables are created
|
||||
@@ -31,7 +34,7 @@ def seed_local_database():
|
||||
id=free_student_id,
|
||||
phone="8888888888",
|
||||
email="free@pragativeer.com",
|
||||
password_hash="mock_hash",
|
||||
password_hash=DEFAULT_SEED_HASH,
|
||||
name="Amit Kumar (Free Tier)",
|
||||
student_class=models.ClassEnum.TWELFTH,
|
||||
medium=models.MediumEnum.ENGLISH,
|
||||
@@ -49,7 +52,7 @@ def seed_local_database():
|
||||
id=pro_student_id,
|
||||
phone="7777777777",
|
||||
email="pro@pragativeer.com",
|
||||
password_hash="mock_hash",
|
||||
password_hash=DEFAULT_SEED_HASH,
|
||||
name="Priya Singh (Pro Tier)",
|
||||
student_class=models.ClassEnum.TWELFTH,
|
||||
medium=models.MediumEnum.ENGLISH,
|
||||
@@ -68,7 +71,7 @@ def seed_local_database():
|
||||
id=neha_id,
|
||||
phone="6666666666",
|
||||
email="neha@pragativeer.com",
|
||||
password_hash="mock_hash",
|
||||
password_hash=DEFAULT_SEED_HASH,
|
||||
name="Neha Verma",
|
||||
student_class=models.ClassEnum.TWELFTH,
|
||||
medium=models.MediumEnum.ENGLISH,
|
||||
@@ -117,7 +120,7 @@ def seed_local_database():
|
||||
id=student_id,
|
||||
phone="9999999999",
|
||||
email="student@pragativeer.com",
|
||||
password_hash="mock_hash",
|
||||
password_hash=DEFAULT_SEED_HASH,
|
||||
name="Rahul Sharma (AI Coach Tier)",
|
||||
student_class=models.ClassEnum.TWELFTH,
|
||||
medium=models.MediumEnum.ENGLISH,
|
||||
|
||||
Reference in New Issue
Block a user