"""Content generation router. Handles post generation via LLM, image generation, and CRUD operations on posts. """ from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from ..auth import get_current_user from ..database import get_db from ..models import AffiliateLink, Character, Post, SystemSetting from ..schemas import ( GenerateContentRequest, GenerateImageRequest, PostResponse, PostUpdate, ) from ..services.content import generate_hashtags, generate_post_text, inject_affiliate_links from ..services.images import get_image_provider from ..services.llm import get_llm_provider router = APIRouter( prefix="/api/content", tags=["content"], dependencies=[Depends(get_current_user)], ) def _get_setting(db: Session, key: str) -> str | None: """Retrieve a system setting value by key.""" setting = db.query(SystemSetting).filter(SystemSetting.key == key).first() if setting is None: return None return setting.value @router.post("/generate", response_model=PostResponse) def generate_content(request: GenerateContentRequest, db: Session = Depends(get_db)): """Generate content for a character using LLM.""" # Validate character exists character = db.query(Character).filter(Character.id == request.character_id).first() if not character: raise HTTPException(status_code=404, detail="Character not found") # Get LLM settings provider_name = request.provider or _get_setting(db, "llm_provider") api_key = _get_setting(db, "llm_api_key") model = request.model or _get_setting(db, "llm_model") if not provider_name: raise HTTPException(status_code=400, detail="LLM provider not configured. Set 'llm_provider' in settings.") if not api_key: raise HTTPException(status_code=400, detail="LLM API key not configured. Set 'llm_api_key' in settings.") # Build character dict for content service char_dict = { "name": character.name, "niche": character.niche, "topics": character.topics or [], "tone": character.tone or "professional", } # Create LLM provider and generate text base_url = _get_setting(db, "llm_base_url") llm = get_llm_provider(provider_name, api_key, model, base_url=base_url) text = generate_post_text( character=char_dict, llm_provider=llm, platform=request.platform, topic_hint=request.topic_hint, ) # Generate hashtags hashtags = generate_hashtags(text, llm, request.platform) # Handle affiliate links affiliate_links_used: list[dict] = [] if request.include_affiliates: links = ( db.query(AffiliateLink) .filter( AffiliateLink.is_active == True, (AffiliateLink.character_id == character.id) | (AffiliateLink.character_id == None), ) .all() ) if links: link_dicts = [ { "url": link.url, "label": link.name, "keywords": link.topics or [], } for link in links ] text, affiliate_links_used = inject_affiliate_links( text, link_dicts, character.topics or [] ) # Create post record post = Post( character_id=character.id, content_type=request.content_type, text_content=text, hashtags=hashtags, affiliate_links_used=affiliate_links_used, llm_provider=provider_name, llm_model=model, platform_hint=request.platform, status="draft", ) db.add(post) db.commit() db.refresh(post) return post @router.post("/generate-image", response_model=PostResponse) def generate_image(request: GenerateImageRequest, db: Session = Depends(get_db)): """Generate an image for a character and attach to a post.""" # Validate character exists character = db.query(Character).filter(Character.id == request.character_id).first() if not character: raise HTTPException(status_code=404, detail="Character not found") # Get image settings provider_name = request.provider or _get_setting(db, "image_provider") api_key = _get_setting(db, "image_api_key") if not provider_name: raise HTTPException(status_code=400, detail="Image provider not configured. Set 'image_provider' in settings.") if not api_key: raise HTTPException(status_code=400, detail="Image API key not configured. Set 'image_api_key' in settings.") # Build prompt from character if not provided prompt = request.prompt if not prompt: style_hint = request.style_hint or "" visual_style = character.visual_style or {} style_desc = visual_style.get("description", "") prompt = ( f"Create a social media image for {character.name}, " f"a content creator in the {character.niche} niche. " f"Style: {style_desc} {style_hint}".strip() ) # Generate image image_provider = get_image_provider(provider_name, api_key) image_url = image_provider.generate(prompt, size=request.size) # Create a new post with the image post = Post( character_id=character.id, content_type="image", image_url=image_url, platform_hint="instagram", status="draft", ) db.add(post) db.commit() db.refresh(post) return post @router.get("/posts", response_model=list[PostResponse]) def list_posts( character_id: int | None = Query(None), status: str | None = Query(None), db: Session = Depends(get_db), ): """List all posts with optional filters.""" query = db.query(Post) if character_id is not None: query = query.filter(Post.character_id == character_id) if status is not None: query = query.filter(Post.status == status) return query.order_by(Post.created_at.desc()).all() @router.get("/posts/{post_id}", response_model=PostResponse) def get_post(post_id: int, db: Session = Depends(get_db)): """Get a single post by ID.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="Post not found") return post @router.put("/posts/{post_id}", response_model=PostResponse) def update_post(post_id: int, data: PostUpdate, db: Session = Depends(get_db)): """Update a post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="Post not found") update_data = data.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(post, key, value) post.updated_at = datetime.utcnow() db.commit() db.refresh(post) return post @router.delete("/posts/{post_id}", status_code=204) def delete_post(post_id: int, db: Session = Depends(get_db)): """Delete a post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="Post not found") db.delete(post) db.commit() @router.post("/posts/{post_id}/approve", response_model=PostResponse) def approve_post(post_id: int, db: Session = Depends(get_db)): """Approve a post (set status to 'approved').""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="Post not found") post.status = "approved" post.updated_at = datetime.utcnow() db.commit() db.refresh(post) return post