| 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) |
| |
| |
| total_active_subscriptions = await db.users.count_documents({"subscription_status": "active"}) |
| |
| |
| 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) |
| |
| 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") |
| |
| |
| now = datetime.utcnow() |
| if plan == "yearly": |
| end_date = now + timedelta(days=365) |
| else: |
| 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 |
| } |
| } |
| ) |
| |
| |
| 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@") |
| else: |
| val = get_setting(key) |
| settings[key] = val |
| |
| |
| 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 |
| |
| |
| if "*****" in value: |
| continue |
| |
| |
| 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())} |
|
|