"""User management routes""" from fastapi import APIRouter, HTTPException from pymongo.errors import DuplicateKeyError, WriteError from db import get_database from models import UserCreate, UserUpdate, User from datetime import datetime from typing import Optional from bson import ObjectId router = APIRouter() @router.post("/register", response_model=dict) async def register_user(user_data: UserCreate): """ Register or get user (idempotent). Uses upsert pattern to ensure one user per email. If user already exists, returns existing user. Called after Firebase Google Auth on frontend. """ db = get_database() try: # Upsert: Update if exists, insert if not result = db.users.update_one( {"email": user_data.email}, { "$setOnInsert": { "email": user_data.email, "displayName": user_data.displayName or user_data.email.split("@")[0], "photoURL": user_data.photoURL, "theme": "light", "createdAt": datetime.utcnow() }, "$set": { "updatedAt": datetime.utcnow() } }, upsert=True ) # Fetch the user (either newly created or existing) user = db.users.find_one({"email": user_data.email}) if not user: raise HTTPException( status_code=500, detail="Failed to retrieve user after upsert") return { "id": str(user["_id"]), "email": user["email"], "displayName": user["displayName"], "photoURL": user.get("photoURL"), "theme": user.get("theme", "light"), "createdAt": user["createdAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat(), "message": "User registered successfully" if result.upserted_id else "User already exists" } except Exception as e: raise HTTPException( status_code=500, detail=f"Registration failed: {str(e)}") @router.get("/by-email/{email}", response_model=dict) async def get_user_by_email(email: str): """Get user profile by email (called after Firebase Auth).""" db = get_database() try: user = db.users.find_one({"email": email}) if not user: raise HTTPException(status_code=404, detail="User not found") return { "id": str(user["_id"]), "email": user["email"], "displayName": user.get("displayName"), "photoURL": user.get("photoURL"), "theme": user.get("theme", "light"), "createdAt": user["createdAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat() } except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to fetch user: {str(e)}") @router.get("/{user_id}", response_model=dict) async def get_user_by_id(user_id: str): """Get user profile by ID.""" db = get_database() try: user = db.users.find_one({"_id": ObjectId(user_id)}) if not user: raise HTTPException(status_code=404, detail="User not found") return { "id": str(user["_id"]), "email": user["email"], "displayName": user.get("displayName"), "photoURL": user.get("photoURL"), "theme": user.get("theme", "light"), "createdAt": user["createdAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat() } except Exception as e: if "invalid ObjectId" in str(e).lower(): raise HTTPException( status_code=400, detail="Invalid user ID format") raise HTTPException( status_code=500, detail=f"Failed to fetch user: {str(e)}") @router.put("/{user_id}", response_model=dict) async def update_user(user_id: str, user_data: UserUpdate): """Update user profile.""" db = get_database() try: # Prepare update data (exclude None values) update_data = user_data.model_dump(exclude_unset=True) update_data["updatedAt"] = datetime.utcnow() result = db.users.update_one( {"_id": ObjectId(user_id)}, {"$set": update_data} ) if result.matched_count == 0: raise HTTPException(status_code=404, detail="User not found") # Fetch and return updated user user = db.users.find_one({"_id": ObjectId(user_id)}) return { "id": str(user["_id"]), "email": user["email"], "displayName": user.get("displayName"), "photoURL": user.get("photoURL"), "theme": user.get("theme", "light"), "createdAt": user["createdAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat(), "message": "User updated successfully" } except Exception as e: if "invalid ObjectId" in str(e).lower(): raise HTTPException( status_code=400, detail="Invalid user ID format") raise HTTPException(status_code=500, detail=f"Update failed: {str(e)}") @router.delete("/{user_id}") async def delete_user(user_id: str): """Delete user account and all associated data.""" db = get_database() try: # Delete user user_result = db.users.delete_one({"_id": ObjectId(user_id)}) if user_result.deleted_count == 0: raise HTTPException(status_code=404, detail="User not found") # Delete all user's entries entry_result = db.entries.delete_many({"userId": ObjectId(user_id)}) return { "message": "User deleted successfully", "user_deleted": user_result.deleted_count, "entries_deleted": entry_result.deleted_count } except Exception as e: if "invalid ObjectId" in str(e).lower(): raise HTTPException( status_code=400, detail="Invalid user ID format") raise HTTPException( status_code=500, detail=f"Deletion failed: {str(e)}") # Delete all entries by user db.entries.delete_many({"userId": user_id}) # Delete user settings db.settings.delete_one({"userId": user_id}) return {"message": "User and associated data deleted"} except Exception as e: raise HTTPException(status_code=500, detail=str(e))