Spaces:
Sleeping
Sleeping
File size: 5,749 Bytes
ecb9f70 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | """User profile synchronization endpoints."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel
from src.api.auth import require_auth
from src.api.deps import get_storage
from src.storage import BodhiStorage
router = APIRouter(prefix="/api/users", tags=["users"])
class UserSyncResponse(BaseModel):
user_id: str
clerk_user_id: str
class UserStatusResponse(BaseModel):
user_id: str
clerk_user_id: str
has_resume: bool
@router.post("/me", response_model=UserSyncResponse)
async def upsert_current_user(
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
user_id = storage.ensure_user_profile_for_clerk(clerk_user_id)
return UserSyncResponse(user_id=user_id, clerk_user_id=clerk_user_id)
@router.get("/me", response_model=UserSyncResponse)
async def get_current_user(
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
user_id = storage.get_user_profile_id_by_clerk_user_id(clerk_user_id)
if not user_id:
user_id = storage.ensure_user_profile_for_clerk(clerk_user_id)
return UserSyncResponse(user_id=user_id, clerk_user_id=clerk_user_id)
@router.get("/me/status", response_model=UserStatusResponse)
async def get_current_user_status(
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
status = storage.get_user_profile_status_by_clerk_user_id(clerk_user_id)
if not status:
user_id = storage.ensure_user_profile_for_clerk(clerk_user_id)
return UserStatusResponse(
user_id=user_id,
clerk_user_id=clerk_user_id,
has_resume=False,
)
user_id, has_resume = status
return UserStatusResponse(
user_id=user_id,
clerk_user_id=clerk_user_id,
has_resume=has_resume,
)
from datetime import datetime
class InterviewHistoryItem(BaseModel):
session_id: str
target_company: str
target_role: str
overall_score: float | None = None
started_at: datetime
ended_at: datetime | None = None
class UserProfileResponse(BaseModel):
clerk_user_id: str
has_resume: bool
resume_data: dict | None = None
resume_file_name: str | None = None
interview_history: list[InterviewHistoryItem]
full_name: str | None = None
experience_level: str | None = None
class UpdateNameRequest(BaseModel):
full_name: str
class UpdateExperienceRequest(BaseModel):
experience_level: str
@router.get("/me/resume/download")
async def download_resume(
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
content, filename = storage.get_user_resume_file(clerk_user_id)
if not content:
raise HTTPException(status_code=404, detail="No resume available to download.")
return Response(content, media_type="application/octet-stream", headers={
"Content-Disposition": f'attachment; filename="{filename or "resume.pdf"}"'
})
@router.get("/me/profile", response_model=UserProfileResponse)
async def get_full_user_profile(
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
# Get user profile status to find user_id and has_resume
status = storage.get_user_profile_status_by_clerk_user_id(clerk_user_id)
if not status:
user_id = storage.ensure_user_profile_for_clerk(clerk_user_id)
has_resume = False
else:
user_id, has_resume = status
# Get resume profile data
resume_data = None
resume_file_name = None
if has_resume:
full_profile = storage.get_user_profile(user_id)
if full_profile and getattr(full_profile, "get", None):
resume_data = full_profile.get("professional_summary")
resume_file_name = full_profile.get("resume_file_name")
# Get interview history
history = storage.get_user_interview_history(clerk_user_id)
# Get full name + experience
full_name = storage.get_user_full_name(clerk_user_id)
experience_level = storage.get_user_experience_level(clerk_user_id)
return UserProfileResponse(
clerk_user_id=clerk_user_id,
has_resume=has_resume,
resume_data=resume_data,
resume_file_name=resume_file_name,
interview_history=history,
full_name=full_name,
experience_level=experience_level,
)
@router.put("/me/name")
async def update_user_name(
body: UpdateNameRequest,
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
"""Update the user's full name in the profile table."""
# Ensure user profile exists
storage.ensure_user_profile_for_clerk(clerk_user_id)
# Update the name
success = storage.update_user_full_name(clerk_user_id, body.full_name)
if not success:
raise HTTPException(status_code=400, detail="Failed to update name")
return {"success": True, "full_name": body.full_name}
@router.put("/me/experience")
async def update_user_experience(
body: UpdateExperienceRequest,
clerk_user_id: str = Depends(require_auth),
storage: BodhiStorage = Depends(get_storage),
):
"""Update the user's explicit experience level in the profile table."""
storage.ensure_user_profile_for_clerk(clerk_user_id)
success = storage.update_user_experience_level(clerk_user_id, body.experience_level)
if not success:
raise HTTPException(status_code=400, detail="Failed to update experience level")
return {"success": True, "experience_level": body.experience_level}
|