arxplorer / src /controller /profile.py
Subhadeep Mandal
Fresh deploy
54eb2ce
Raw
History Blame Contribute Delete
9.88 kB
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError, DBAPIError
from fastapi import HTTPException, UploadFile
from ..database.db import session_pool
from ..errors import DatabaseConnectionError
from ..model.profile import Profile
from ..schema.profile import ProfileCreate, ProfileUpdate, ProfileResponse
from ..core.storage.supabase import SupabaseStorage
from ..core.logger import SingletonLogger
logger = SingletonLogger().get_logger()
# Instantiate storage provider (Supabase)
storage = SupabaseStorage.from_env()
async def get_profile(user_id: int) -> ProfileResponse:
"""Get user profile"""
try:
async with session_pool() as session:
result = await session.execute(
select(Profile).where(Profile.user_id == user_id)
)
profile = result.scalar_one_or_none()
if not profile:
raise HTTPException(status_code=404, detail="Profile not found")
# Clean invalid placeholder values
def clean_value(value: str | None) -> str | None:
if value in ["string", "null", "undefined", "None"]:
return None
return value
# Convert to dict and add avatar_url
profile_dict = {
"id": profile.id,
"user_id": profile.user_id,
"phone": clean_value(profile.phone),
"bio": clean_value(profile.bio),
"avatar_url": (
storage.get_file_url(profile.avatar) if profile.avatar else None
),
"topic_preferences": clean_value(profile.topic_preferences),
}
return ProfileResponse(**profile_dict)
except HTTPException:
raise
except DBAPIError as e:
logger.exception(
f"Database connection error fetching profile for user_id={user_id}: {str(e)}"
)
raise DatabaseConnectionError(str(e))
except SQLAlchemyError as e:
logger.error(f"Database error fetching profile for user_id={user_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to retrieve profile")
except Exception as e:
logger.error(
f"Unexpected error fetching profile for user_id={user_id}: {str(e)}"
)
raise HTTPException(status_code=500, detail="Internal server error")
async def create_profile(user_id: int, profile_data: ProfileCreate) -> ProfileResponse:
"""Create user profile"""
try:
async with session_pool() as session:
# Check if profile already exists
result = await session.execute(
select(Profile).where(Profile.user_id == user_id)
)
if result.scalar_one_or_none():
logger.warning(
f"Attempted to create duplicate profile for user_id={user_id}"
)
raise HTTPException(status_code=400, detail="Profile already exists")
# Clean invalid placeholder values
def clean_value(value: str | None) -> str | None:
if value in ["string", "null", "undefined", "None"]:
return None
return value
data_dict = profile_data.model_dump()
data_dict["phone"] = clean_value(data_dict.get("phone"))
data_dict["bio"] = clean_value(data_dict.get("bio"))
data_dict["topic_preferences"] = clean_value(
data_dict.get("topic_preferences")
)
profile = Profile(user_id=user_id, **data_dict)
session.add(profile)
await session.commit()
await session.refresh(profile)
# Return with avatar URL
profile_dict = {
"id": profile.id,
"user_id": profile.user_id,
"phone": clean_value(profile.phone),
"bio": clean_value(profile.bio),
"avatar_url": (
storage.get_file_url(profile.avatar) if profile.avatar else None
),
"topic_preferences": clean_value(profile.topic_preferences),
}
return ProfileResponse(**profile_dict)
except HTTPException:
raise
except DBAPIError as e:
logger.exception(
f"Database connection error creating profile for user_id={user_id}: {str(e)}"
)
raise DatabaseConnectionError(str(e))
except SQLAlchemyError as e:
logger.error(f"Database error creating profile for user_id={user_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to create profile")
except Exception as e:
logger.error(
f"Unexpected error creating profile for user_id={user_id}: {str(e)}"
)
raise HTTPException(status_code=500, detail="Internal server error")
async def update_profile(user_id: int, profile_data: ProfileUpdate) -> ProfileResponse:
"""Update user profile"""
try:
async with session_pool() as session:
result = await session.execute(
select(Profile).where(Profile.user_id == user_id)
)
profile = result.scalar_one_or_none()
if not profile:
raise HTTPException(status_code=404, detail="Profile not found")
# Clean invalid placeholder values
def clean_value(value: str | None) -> str | None:
if value in ["string", "null", "undefined", "None"]:
return None
return value
update_data = profile_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
if field in ["phone", "bio", "topic_preferences"]:
value = clean_value(value)
setattr(profile, field, value)
await session.commit()
await session.refresh(profile)
# Return with avatar URL
profile_dict = {
"id": profile.id,
"user_id": profile.user_id,
"phone": clean_value(profile.phone),
"bio": clean_value(profile.bio),
"avatar_url": (
storage.get_file_url(profile.avatar) if profile.avatar else None
),
"topic_preferences": clean_value(profile.topic_preferences),
}
return ProfileResponse(**profile_dict)
except HTTPException:
raise
except DBAPIError as e:
logger.exception(
f"Database connection error updating profile for user_id={user_id}: {str(e)}"
)
raise DatabaseConnectionError(str(e))
except SQLAlchemyError as e:
logger.error(f"Database error updating profile for user_id={user_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to update profile")
except Exception as e:
logger.error(
f"Unexpected error updating profile for user_id={user_id}: {str(e)}"
)
raise HTTPException(status_code=500, detail="Internal server error")
async def upload_avatar(user_id: int, file: UploadFile) -> ProfileResponse:
"""Upload avatar for user profile"""
try:
async with session_pool() as session:
result = await session.execute(
select(Profile).where(Profile.user_id == user_id)
)
profile = result.scalar_one_or_none()
if not profile:
raise HTTPException(status_code=404, detail="Profile not found")
# Delete old avatar if exists
if profile.avatar:
try:
await storage.delete_file(profile.avatar)
except Exception as e:
logger.warning(
f"Failed to delete old avatar for user_id={user_id}: {str(e)}"
)
# Upload new avatar
avatar_key = await storage.upload_file(file, user_id, "avatar")
# Update profile
profile.avatar = avatar_key
await session.commit()
await session.refresh(profile)
# Clean invalid placeholder values
def clean_value(value: str | None) -> str | None:
if value in ["string", "null", "undefined", "None"]:
return None
return value
# Return with avatar URL
profile_dict = {
"id": profile.id,
"user_id": profile.user_id,
"phone": clean_value(profile.phone),
"bio": clean_value(profile.bio),
"avatar_url": (
storage.get_file_url(profile.avatar) if profile.avatar else None
),
"topic_preferences": clean_value(profile.topic_preferences),
}
return ProfileResponse(**profile_dict)
except HTTPException:
raise
except DBAPIError as e:
logger.exception(
f"Database connection error uploading avatar for user_id={user_id}: {str(e)}"
)
raise DatabaseConnectionError(str(e))
except SQLAlchemyError as e:
logger.error(f"Database error uploading avatar for user_id={user_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to upload avatar")
except Exception as e:
logger.error(
f"Unexpected error uploading avatar for user_id={user_id}: {str(e)}"
)
raise HTTPException(status_code=500, detail="Failed to upload avatar")