from fastapi import APIRouter, UploadFile, File, Form, HTTPException from fastapi.responses import JSONResponse from typing import List, Dict, Optional import io from app.services.r2_service import r2_service from app.services.ai_service import ai_service router = APIRouter() @router.get("/templates", response_model=List[str]) async def get_templates(): """ Get list of available resume templates from R2 """ try: templates = r2_service.list_templates() return templates except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to fetch templates: {str(e)}" ) @router.get("/templates/{template_name}") async def get_template_content(template_name: str): """ Get the HTML content of a specific template """ try: content = r2_service.get_template_content(template_name) if content is None: raise HTTPException( status_code=404, detail=f"Template '{template_name}' not found" ) return {"content": content} except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to fetch template content: {str(e)}" ) @router.post("/convert") async def convert_resume( file: UploadFile = File(...), template_name: str = Form(...) ): """ Convert a resume file using the specified template 1. Extract text from resume using Gemini AI 2. Get template content from R2 3. Generate formatted HTML using Gemini AI 4. Upload HTML and PDF to R2 5. Return URLs for download """ try: # Validate file type allowed_types = [ 'application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ] if file.content_type not in allowed_types: raise HTTPException( status_code=400, detail="Invalid file type. Only PDF and DOCX files are allowed." ) # Read file content file_content = await file.read() # Step 1: Extract text from resume resume_text = await ai_service.extract_text_from_resume( file_content, file.content_type ) if not resume_text: raise HTTPException( status_code=500, detail="Failed to extract text from resume" ) # Step 2: Get template content template_html = r2_service.get_template_content(template_name) if not template_html: raise HTTPException( status_code=404, detail=f"Template '{template_name}' not found" ) # Step 3: Generate formatted HTML generated_html = await ai_service.generate_html_from_template( resume_text, template_html ) if not generated_html: raise HTTPException( status_code=500, detail="Failed to generate formatted HTML" ) # Step 4: Upload HTML to R2 base_filename = file.filename.rsplit('.', 1)[0] html_filename = f"{base_filename}_{template_name}.html" html_url = r2_service.upload_converted_file( generated_html.encode('utf-8'), html_filename, 'text/html', metadata={ 'original_filename': file.filename, 'template': template_name } ) if not html_url: raise HTTPException( status_code=500, detail="Failed to upload HTML to storage" ) # Return response return { "success": True, "html_url": html_url, "html_content": generated_html, "message": "Resume converted successfully" } except HTTPException: raise except Exception as e: print(f"Error converting resume: {e}") raise HTTPException( status_code=500, detail=f"An error occurred during conversion: {str(e)}" ) @router.get("/history", response_model=List[Dict]) async def get_conversion_history(limit: int = 50): """ Get list of previously converted resumes from R2 """ try: files = r2_service.list_converted_resumes(limit=limit) return files except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to fetch conversion history: {str(e)}" ) @router.get("/download/{file_key:path}") async def get_download_url(file_key: str): """ Get a presigned download URL for a file """ try: url = r2_service.get_file_url(file_key) if not url: raise HTTPException( status_code=404, detail="File not found" ) return {"url": url} except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to generate download URL: {str(e)}" )