"""Comment management router. Handles fetching, reviewing, and replying to comments on published 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 Comment, Post, ScheduledPost, SocialAccount, SystemSetting, User from ..plan_limits import get_plan from ..schemas import CommentAction, CommentResponse from ..services.llm import get_llm_provider from ..services.social import get_publisher router = APIRouter( prefix="/api/comments", tags=["comments"], ) @router.get("/", response_model=list[CommentResponse]) def list_comments( platform: str | None = Query(None), reply_status: str | None = Query(None), scheduled_post_id: int | None = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """List comments with optional filters.""" query = db.query(Comment) if platform is not None: query = query.filter(Comment.platform == platform) if reply_status is not None: query = query.filter(Comment.reply_status == reply_status) if scheduled_post_id is not None: query = query.filter(Comment.scheduled_post_id == scheduled_post_id) return query.order_by(Comment.created_at.desc()).all() @router.get("/pending", response_model=list[CommentResponse]) def list_pending_comments( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get only pending comments (reply_status='pending').""" plan = get_plan(current_user) if not plan.get("comments_management"): raise HTTPException( status_code=403, detail={"message": "Gestione commenti disponibile solo con Pro.", "upgrade_required": True}, ) return ( db.query(Comment) .filter(Comment.reply_status == "pending") .order_by(Comment.created_at.desc()) .all() ) @router.get("/{comment_id}", response_model=CommentResponse) def get_comment( comment_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get a single comment by ID.""" comment = db.query(Comment).filter(Comment.id == comment_id).first() if not comment: raise HTTPException(status_code=404, detail="Comment not found") return comment @router.post("/{comment_id}/action", response_model=CommentResponse) def action_on_comment( comment_id: int, data: CommentAction, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Take action on a comment: approve, edit, or ignore.""" plan = get_plan(current_user) if not plan.get("comments_management"): raise HTTPException( status_code=403, detail={"message": "Gestione commenti disponibile solo con Pro.", "upgrade_required": True}, ) comment = db.query(Comment).filter(Comment.id == comment_id).first() if not comment: raise HTTPException(status_code=404, detail="Comment not found") if data.action == "approve": comment.approved_reply = comment.ai_suggested_reply comment.reply_status = "approved" elif data.action == "edit": if not data.reply_text: raise HTTPException(status_code=400, detail="reply_text required for edit action") comment.approved_reply = data.reply_text comment.reply_status = "approved" elif data.action == "ignore": comment.reply_status = "ignored" else: raise HTTPException(status_code=400, detail=f"Unknown action '{data.action}'. Use: approve, edit, ignore") db.commit() db.refresh(comment) return comment @router.post("/{comment_id}/reply", response_model=CommentResponse) def reply_to_comment( comment_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Send the approved reply via the social platform API.""" comment = db.query(Comment).filter(Comment.id == comment_id).first() if not comment: raise HTTPException(status_code=404, detail="Comment not found") if not comment.approved_reply: raise HTTPException(status_code=400, detail="No approved reply to send. Use /action first.") if not comment.external_comment_id: raise HTTPException(status_code=400, detail="No external comment ID available for reply") if not comment.scheduled_post_id: raise HTTPException(status_code=400, detail="Comment is not linked to a scheduled post") scheduled = ( db.query(ScheduledPost) .filter(ScheduledPost.id == comment.scheduled_post_id) .first() ) if not scheduled: raise HTTPException(status_code=404, detail="Associated scheduled post not found") post = db.query(Post).filter(Post.id == scheduled.post_id).first() if not post: raise HTTPException(status_code=404, detail="Associated post not found") account = ( db.query(SocialAccount) .filter( SocialAccount.character_id == post.character_id, SocialAccount.platform == comment.platform, SocialAccount.is_active == True, ) .first() ) if not account: raise HTTPException( status_code=400, detail=f"No active {comment.platform} account found for this character", ) kwargs: dict = {} if account.platform == "facebook": kwargs["page_id"] = account.page_id elif account.platform == "instagram": kwargs["ig_user_id"] = account.account_id or (account.extra_data or {}).get("ig_user_id") try: publisher = get_publisher(account.platform, account.access_token, **kwargs) success = publisher.reply_to_comment(comment.external_comment_id, comment.approved_reply) if not success: raise RuntimeError("Platform returned failure for reply") comment.reply_status = "replied" comment.replied_at = datetime.utcnow() db.commit() db.refresh(comment) return comment except (RuntimeError, ValueError) as e: raise HTTPException(status_code=502, detail=f"Failed to send reply: {e}") @router.post("/fetch/{platform}") def fetch_comments( platform: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Fetch new comments from a platform for all published posts.""" published_posts = ( db.query(ScheduledPost) .filter( ScheduledPost.platform == platform, ScheduledPost.status == "published", ScheduledPost.external_post_id != None, ) .all() ) if not published_posts: return {"new_comments": 0, "message": f"No published posts found for {platform}"} llm_provider_name = None llm_api_key = None llm_model = None for key in ("llm_provider", "llm_api_key", "llm_model"): setting = ( db.query(SystemSetting) .filter(SystemSetting.key == key, SystemSetting.user_id == current_user.id) .first() ) if not setting: setting = db.query(SystemSetting).filter(SystemSetting.key == key, SystemSetting.user_id == None).first() if setting: if key == "llm_provider": llm_provider_name = setting.value elif key == "llm_api_key": llm_api_key = setting.value elif key == "llm_model": llm_model = setting.value llm = None if llm_provider_name and llm_api_key: try: llm = get_llm_provider(llm_provider_name, llm_api_key, llm_model) except ValueError: pass new_comment_count = 0 for scheduled in published_posts: post = db.query(Post).filter(Post.id == scheduled.post_id).first() if not post: continue account = ( db.query(SocialAccount) .filter( SocialAccount.character_id == post.character_id, SocialAccount.platform == platform, SocialAccount.is_active == True, ) .first() ) if not account or not account.access_token: continue kwargs: dict = {} if account.platform == "facebook": kwargs["page_id"] = account.page_id elif account.platform == "instagram": kwargs["ig_user_id"] = account.account_id or (account.extra_data or {}).get("ig_user_id") try: publisher = get_publisher(account.platform, account.access_token, **kwargs) comments = publisher.get_comments(scheduled.external_post_id) except (RuntimeError, ValueError): continue for ext_comment in comments: ext_id = ext_comment.get("id", "") if not ext_id: continue existing = ( db.query(Comment) .filter(Comment.external_comment_id == ext_id) .first() ) if existing: continue ai_reply = None if llm: try: system_prompt = ( f"You are managing social media comments for a content creator. " f"Write a friendly, on-brand reply to this comment. " f"Keep it concise (1-2 sentences). Be authentic and engaging." ) prompt = ( f"Comment by {ext_comment.get('author', 'someone')}: " f"\"{ext_comment.get('text', '')}\"\n\n" f"Write a reply:" ) ai_reply = llm.generate(prompt, system=system_prompt) except RuntimeError: pass comment = Comment( scheduled_post_id=scheduled.id, platform=platform, external_comment_id=ext_id, author_name=ext_comment.get("author", "Unknown"), author_id=ext_comment.get("id", ""), content=ext_comment.get("text", ""), ai_suggested_reply=ai_reply, reply_status="pending", ) db.add(comment) new_comment_count += 1 db.commit() return {"new_comments": new_comment_count, "message": f"Fetched {new_comment_count} new comments from {platform}"}