from fastapi import APIRouter, HTTPException, status, Depends, Header, Path, Body from pydantic import BaseModel from models.preorder import get_all_preorders, get_preorder_count, delete_preorder, update_preorder_status from db import get_database from utils.email_utils import send_subscription_approval_email from datetime import datetime, timedelta from bson import ObjectId from utils.config_loader import get_setting, save_settings_to_file router = APIRouter() ADMIN_TOKEN = "admin_token_123" class AdminLogin(BaseModel): password: str class StatusUpdateRequest(BaseModel): status: str class SettingsModel(BaseModel): ADMIN_PASSWORD: str | None = None MONGODB_URI: str | None = None DB_NAME: str | None = None TWILIO_ACCOUNT_SID: str | None = None TWILIO_AUTH_TOKEN: str | None = None TWILIO_NUMBER: str | None = None GMAIL_USER: str | None = None GMAIL_PASS: str | None = None OPENAI_API_KEY: str | None = None SERPAPI_API_KEY: str | None = None FRONTEND_URL: str | None = None STRIPE_SECRET_KEY: str | None = None STRIPE_PUBLISHABLE_KEY: str | None = None def mask_credential(value: str | None) -> str: if not value: return "" if len(value) <= 10: return "********" return value[:3] + "*****" + value[-3:] @router.post("/admin/login") async def admin_login(credentials: AdminLogin): current_password = get_setting("ADMIN_PASSWORD", "admin123@") if credentials.password != current_password: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid password") return {"token": ADMIN_TOKEN} def admin_auth(Authorization: str = Header(...)): token_parts = Authorization.split(" ") if len(token_parts) != 2 or token_parts[0].lower() != "bearer" or token_parts[1] != ADMIN_TOKEN: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or missing admin token") return token_parts[1] @router.get("/admin/dashboard") async def admin_dashboard( db=Depends(get_database), token: str = Depends(admin_auth) ): total_customers = await get_preorder_count(db) customers = await get_all_preorders(db) # Count users with active subscriptions total_active_subscriptions = await db.users.count_documents({"subscription_status": "active"}) # Get app download stats download_record = await db.analytics.find_one({"key": "app_downloads"}) total_downloads = download_record.get("count", 0) if download_record else 0 return { "total_customers": total_customers, "customers": customers, "total_active_subscriptions": total_active_subscriptions, "total_downloads": total_downloads } @router.delete("/admin/preorder/{preorder_id}") async def admin_delete_preorder( preorder_id: str = Path(...), db=Depends(get_database), token: str = Depends(admin_auth) ): deleted_count = await delete_preorder(db, preorder_id) if deleted_count == 0: raise HTTPException(status_code=404, detail="Preorder not found") return {"success": True} @router.patch("/admin/preorder/{preorder_id}") async def admin_update_preorder_status( preorder_id: str = Path(...), req: StatusUpdateRequest = Body(...), db=Depends(get_database), token: str = Depends(admin_auth) ): try: modified_count = await update_preorder_status(db, preorder_id, req.status) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) if modified_count == 0: raise HTTPException(status_code=404, detail="Preorder not found or status unchanged") return {"success": True, "status": req.status} @router.get("/admin/users") async def admin_get_users( db=Depends(get_database), token: str = Depends(admin_auth) ): users_cursor = db.users.find({}, {"hashed_password": 0}) users = await users_cursor.to_list(length=1000) # Convert ObjectId to str for user in users: user["id"] = str(user["_id"]) del user["_id"] return users @router.post("/admin/users/{user_id}/approve") async def admin_approve_subscription( user_id: str = Path(...), db=Depends(get_database), token: str = Depends(admin_auth) ): user = await db.users.find_one({"_id": ObjectId(user_id)}) if not user: raise HTTPException(status_code=404, detail="User not found") plan = user.get("subscription_plan", "monthly") # Calculate end date now = datetime.utcnow() if plan == "yearly": end_date = now + timedelta(days=365) else: # monthly or default end_date = now + timedelta(days=30) await db.users.update_one( {"_id": ObjectId(user_id)}, { "$set": { "subscription_status": "active", "subscription_end_date": end_date, "updated_at": now } } ) # Send email await send_subscription_approval_email(user["email"], user["full_name"], plan, end_date.strftime("%Y-%m-%d")) return {"success": True, "msg": "Subscription approved"} @router.get("/admin/settings") async def get_admin_settings(token: str = Depends(admin_auth)): keys = [ "ADMIN_PASSWORD", "MONGODB_URI", "DB_NAME", "TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_NUMBER", "GMAIL_USER", "GMAIL_PASS", "OPENAI_API_KEY", "SERPAPI_API_KEY", "FRONTEND_URL", "STRIPE_SECRET_KEY", "STRIPE_PUBLISHABLE_KEY" ] settings = {} for key in keys: if key == "ADMIN_PASSWORD": val = get_setting(key, "admin123@") # Default if not set else: val = get_setting(key) settings[key] = val # Create a separate dict for the response to apply masking response_settings = {} for k, v in settings.items(): if k in ["FRONTEND_URL", "DB_NAME", "GMAIL_USER", "TWILIO_NUMBER"]: response_settings[k] = v or "" else: response_settings[k] = mask_credential(v) return response_settings @router.post("/admin/settings") async def update_admin_settings( settings: SettingsModel, token: str = Depends(admin_auth) ): updates = {} for key, value in settings.dict().items(): if value is None: continue # Check if it's a masked value (ignored) if "*****" in value: continue # If it's different from current, update it updates[key] = value if updates: success = save_settings_to_file(updates) if not success: raise HTTPException(status_code=500, detail="Failed to save settings") return {"success": True, "updated_keys": list(updates.keys())}