Medisync / routers /admin.py
anique-1's picture
Deploy MediSync AI Backend with APK to Hugging Face
903c699
Raw
History Blame Contribute Delete
6.83 kB
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())}