attempt to fix profile picture error

This commit is contained in:
2026-03-16 13:23:58 +01:00
parent 46793f4acf
commit 17799a05ce
9 changed files with 219 additions and 25 deletions

View File

@@ -47,6 +47,7 @@ from src.agents.link_topic_builder import LinkTopicBuilderAgent
from src.agents.strategy_importer import StrategyImporterAgent
from src.services.post_insights_service import compute_post_insights, refresh_post_insights_for_account
from src.services.insights_summary_service import generate_insights_summary
from src.utils.post_cleanup import sanitize_post_content
# Router for user frontend
user_router = APIRouter(tags=["user"])
@@ -68,16 +69,16 @@ async def get_user_profile_picture(user_id: UUID) -> Optional[str]:
Note: session.linkedin_picture (OAuth login) should be checked by caller first.
"""
# Check for connected LinkedIn account first
linkedin_account = await db.get_linkedin_account(user_id)
if linkedin_account and linkedin_account.is_active and linkedin_account.linkedin_picture:
return linkedin_account.linkedin_picture
# Fall back to profile picture from setup process
# Prefer cached profile picture (Supabase) to avoid LinkedIn hotlink blocking
profile = await db.get_profile(user_id)
if profile and profile.profile_picture:
return profile.profile_picture
# Fall back to connected LinkedIn account
linkedin_account = await db.get_linkedin_account(user_id)
if linkedin_account and linkedin_account.is_active and linkedin_account.linkedin_picture:
return linkedin_account.linkedin_picture
return None
@@ -103,6 +104,48 @@ async def get_user_avatar(session: UserSession, user_id: UUID) -> Optional[str]:
return None
async def cache_linkedin_picture(
picture_url: Optional[str],
user_id: UUID,
http_client: Optional["httpx.AsyncClient"] = None
) -> Optional[str]:
"""Download LinkedIn profile picture once and store in Supabase."""
if not picture_url:
return None
try:
import httpx
close_client = False
client = http_client
if client is None:
client = httpx.AsyncClient(timeout=30.0)
close_client = True
headers = {
"User-Agent": "Mozilla/5.0",
"Referer": "https://www.linkedin.com/",
"Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
}
response = await client.get(picture_url, headers=headers)
if response.status_code != 200 or not response.content:
return picture_url
content_type = response.headers.get("content-type", "image/jpeg")
uploaded_url = await storage.upload_media(
file_content=response.content,
content_type=content_type,
user_id=user_id
)
if close_client:
await client.aclose()
return uploaded_url or picture_url
except Exception as e:
logger.warning(f"Failed to cache LinkedIn picture: {e}")
return picture_url
async def get_employee_permissions_or_default(user_id: UUID, company_id: UUID) -> dict:
"""Get employee permissions for a company, returning all-true defaults if no row exists."""
@@ -2926,6 +2969,7 @@ async def linkedin_callback(
linkedin_user_id = userinfo.get("sub")
linkedin_name = userinfo.get("name", "")
linkedin_picture = userinfo.get("picture")
cached_picture = await cache_linkedin_picture(linkedin_picture, UUID(session.user_id), http_client=client)
# Get vanity name if available (from profile API - optional)
linkedin_vanity_name = None
@@ -2955,7 +2999,7 @@ async def linkedin_callback(
"linkedin_user_id": linkedin_user_id,
"linkedin_vanity_name": linkedin_vanity_name,
"linkedin_name": linkedin_name,
"linkedin_picture": linkedin_picture,
"linkedin_picture": cached_picture or linkedin_picture,
"access_token": encrypted_access,
"refresh_token": encrypted_refresh,
"token_expires_at": datetime.now(timezone.utc) + timedelta(seconds=expires_in),
@@ -2973,7 +3017,7 @@ async def linkedin_callback(
linkedin_user_id=linkedin_user_id,
linkedin_vanity_name=linkedin_vanity_name,
linkedin_name=linkedin_name,
linkedin_picture=linkedin_picture,
linkedin_picture=cached_picture or linkedin_picture,
access_token=encrypted_access,
refresh_token=encrypted_refresh,
token_expires_at=datetime.now(timezone.utc) + timedelta(seconds=expires_in),
@@ -2982,6 +3026,12 @@ async def linkedin_callback(
await db.create_linkedin_account(new_account)
logger.info(f"Created LinkedIn account for user {session.user_id}")
if cached_picture:
try:
await db.update_profile(UUID(session.user_id), {"profile_picture": cached_picture})
except Exception as e:
logger.warning(f"Failed to update profile picture: {e}")
# Clear state cookie and redirect to settings
response = RedirectResponse(url="/settings?success=linkedin_connected", status_code=302)
response.delete_cookie("linkedin_oauth_state")
@@ -4333,6 +4383,7 @@ async def chat_generate_post(request: Request):
company_strategy=company_strategy,
strategy_weight=post_type.strategy_weight
)
post_content = sanitize_post_content(post_content)
# Generate conversation ID
import uuid
@@ -4446,6 +4497,7 @@ async def chat_refine_post(request: Request):
company_strategy=company_strategy,
strategy_weight=getattr(post_type, 'strategy_weight', 0.5)
)
refined_post = sanitize_post_content(refined_post)
return JSONResponse({
"success": True,
@@ -4470,7 +4522,7 @@ async def chat_save_post(request: Request):
try:
data = await request.json()
post_content = data.get("post_content", "").strip()
post_content = sanitize_post_content(data.get("post_content", "").strip())
post_type_id = data.get("post_type_id")
chat_history = data.get("chat_history", [])
@@ -4501,7 +4553,7 @@ async def chat_save_post(request: Request):
for item in chat_history:
if 'ai' in item and item['ai']:
writer_versions.append(item['ai'])
writer_versions.append(sanitize_post_content(item['ai']))
# Store user feedback as "critic feedback"
if 'user' in item and item['user']:
critic_feedback_list.append({
@@ -4620,7 +4672,7 @@ async def update_chat_post(request: Request, post_id: str):
post_uuid = UUID(post_id)
data = await request.json()
post_content = data.get("post_content", "").strip()
post_content = sanitize_post_content(data.get("post_content", "").strip())
chat_history = data.get("chat_history", [])
if not post_content:
@@ -4641,7 +4693,7 @@ async def update_chat_post(request: Request, post_id: str):
for item in chat_history:
if 'ai' in item and item['ai']:
writer_versions.append(item['ai'])
writer_versions.append(sanitize_post_content(item['ai']))
# Store user feedback as "critic feedback"
if 'user' in item and item['user']:
critic_feedback_list.append({
@@ -4751,6 +4803,7 @@ async def company_chat_generate_post(request: Request):
company_strategy=company_strategy,
strategy_weight=post_type.strategy_weight
)
post_content = sanitize_post_content(post_content)
return JSONResponse({
"success": True,
@@ -4777,7 +4830,7 @@ async def company_chat_save_post(request: Request):
try:
data = await request.json()
employee_id = data.get("employee_id")
post_content = data.get("post_content", "").strip()
post_content = sanitize_post_content(data.get("post_content", "").strip())
post_type_id = data.get("post_type_id")
chat_history = data.get("chat_history", [])
topic_title = data.get("topic_title", post_content[:80] if post_content else "Chat Post")
@@ -4794,7 +4847,7 @@ async def company_chat_save_post(request: Request):
if not perms.get("can_create_posts", True):
return JSONResponse({"success": False, "error": "Keine Berechtigung"}, status_code=403)
writer_versions = [item['ai'] for item in chat_history if 'ai' in item and item['ai']]
writer_versions = [sanitize_post_content(item['ai']) for item in chat_history if 'ai' in item and item['ai']]
critic_feedback_list = []
for item in chat_history:
if 'user' in item and item['user']: