Automated Action 3e90deba20 Fix SQLite migration issues by removing non-constant defaults
Changes:
- Removed server_default with CURRENT_TIMESTAMP from User model (SQLite doesn't support non-constant defaults in ALTER TABLE)
- Updated migrations 002 and 003 to add datetime columns without server defaults
- Added manual timestamp updates using SQLite's datetime('now') function in migrations
- Modified User model to handle timestamps in Python code instead of database defaults
- Updated profile routes to manually set updated_at timestamps on profile changes
- Enhanced User model __init__ to set default timestamps for new users

This resolves the 'Cannot add a column with non-constant default' SQLite error
while maintaining proper timestamp functionality.
2025-06-24 19:53:37 +00:00

238 lines
8.0 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 datetime import datetime
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
# Update the timestamp manually
current_user.updated_at = datetime.utcnow()
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)
current_user.updated_at = datetime.utcnow()
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
current_user.updated_at = datetime.utcnow()
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"
)