Automated Action 9947e2629c Add comprehensive user profile management system
Features:
- Extended User model with profile fields (first_name, last_name, phone, bio, preferred_language, timezone)
- Complete profile endpoints for viewing and updating user information
- Password update functionality with current password verification
- Email update functionality with duplicate email checking
- Account deletion endpoint
- Database migration for new profile fields
- Enhanced logging and error handling for all profile operations
- Updated API documentation with profile endpoints

Profile fields include:
- Personal information (name, phone, bio)
- Preferences (language, timezone)
- Account management (password, email updates)
- Timestamps (created_at, updated_at)
2025-06-24 19:40:23 +00:00

232 lines
7.8 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from typing import Optional
import logging
from app.db.session import get_db
from app.models.user import User
from app.utils.auth import get_current_user, get_password_hash, verify_password
logger = logging.getLogger(__name__)
router = APIRouter()
class ProfileResponse(BaseModel):
id: int
email: str
first_name: Optional[str]
last_name: Optional[str]
phone: Optional[str]
bio: Optional[str]
preferred_language: str
timezone: str
created_at: str
updated_at: str
class Config:
orm_mode = True
class ProfileUpdate(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
phone: Optional[str] = None
bio: Optional[str] = None
preferred_language: Optional[str] = None
timezone: Optional[str] = None
class PasswordUpdate(BaseModel):
current_password: str
new_password: str
class EmailUpdate(BaseModel):
new_email: EmailStr
password: str
@router.get("/", response_model=ProfileResponse)
async def get_profile(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
logger.info(f"Profile requested for user: {current_user.email}")
return ProfileResponse(
id=current_user.id,
email=current_user.email,
first_name=current_user.first_name,
last_name=current_user.last_name,
phone=current_user.phone,
bio=current_user.bio,
preferred_language=current_user.preferred_language or "en",
timezone=current_user.timezone or "UTC",
created_at=str(current_user.created_at),
updated_at=str(current_user.updated_at) if hasattr(current_user, 'updated_at') and current_user.updated_at else str(current_user.created_at)
)
@router.put("/", response_model=ProfileResponse)
async def update_profile(
profile_data: ProfileUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
logger.info(f"Profile update requested for user: {current_user.email}")
# Update only provided fields
if profile_data.first_name is not None:
current_user.first_name = profile_data.first_name
if profile_data.last_name is not None:
current_user.last_name = profile_data.last_name
if profile_data.phone is not None:
current_user.phone = profile_data.phone
if profile_data.bio is not None:
current_user.bio = profile_data.bio
if profile_data.preferred_language is not None:
current_user.preferred_language = profile_data.preferred_language
if profile_data.timezone is not None:
current_user.timezone = profile_data.timezone
db.commit()
db.refresh(current_user)
logger.info(f"Profile updated successfully for user: {current_user.email}")
return ProfileResponse(
id=current_user.id,
email=current_user.email,
first_name=current_user.first_name,
last_name=current_user.last_name,
phone=current_user.phone,
bio=current_user.bio,
preferred_language=current_user.preferred_language or "en",
timezone=current_user.timezone or "UTC",
created_at=str(current_user.created_at),
updated_at=str(current_user.updated_at) if hasattr(current_user, 'updated_at') and current_user.updated_at else str(current_user.created_at)
)
except Exception as e:
logger.error(f"Profile update error for {current_user.email}: {str(e)}")
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal server error during profile update"
)
@router.put("/password")
async def update_password(
password_data: PasswordUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
logger.info(f"Password update requested for user: {current_user.email}")
# Verify current password
if not verify_password(password_data.current_password, current_user.password_hash):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Current password is incorrect"
)
# Validate new password
if len(password_data.new_password) < 6:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="New password must be at least 6 characters long"
)
# Update password
current_user.password_hash = get_password_hash(password_data.new_password)
db.commit()
logger.info(f"Password updated successfully for user: {current_user.email}")
return {"message": "Password updated successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Password update error for {current_user.email}: {str(e)}")
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal server error during password update"
)
@router.put("/email")
async def update_email(
email_data: EmailUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
logger.info(f"Email update requested for user: {current_user.email}")
# Verify password
if not verify_password(email_data.password, current_user.password_hash):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Password is incorrect"
)
# Check if new email is already taken
existing_user = db.query(User).filter(User.email == email_data.new_email).first()
if existing_user and existing_user.id != current_user.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
old_email = current_user.email
current_user.email = email_data.new_email
db.commit()
logger.info(f"Email updated successfully from {old_email} to {email_data.new_email}")
return {"message": "Email updated successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Email update error for {current_user.email}: {str(e)}")
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal server error during email update"
)
@router.delete("/")
async def delete_account(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
logger.info(f"Account deletion requested for user: {current_user.email}")
# Note: In a production environment, you might want to:
# 1. Soft delete instead of hard delete
# 2. Clean up related data (videos, transcriptions, etc.)
# 3. Add confirmation mechanisms
user_email = current_user.email
db.delete(current_user)
db.commit()
logger.info(f"Account deleted successfully for user: {user_email}")
return {"message": "Account deleted successfully"}
except Exception as e:
logger.error(f"Account deletion error for {current_user.email}: {str(e)}")
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal server error during account deletion"
)