Initial commit: Leopost Full — merge di Leopost, Post Generator e Autopilot OS
- Backend FastAPI con multi-LLM (Claude/OpenAI/Gemini) - Publishing su Facebook, Instagram, YouTube, TikTok - Calendario editoriale con awareness levels (PAS, AIDA, BAB...) - Design system Editorial Fresh (Fraunces + DM Sans) - Scheduler automatico, gestione commenti AI, affiliate links Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
225
backend/app/routers/content.py
Normal file
225
backend/app/routers/content.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""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
|
||||
llm = get_llm_provider(provider_name, api_key, model)
|
||||
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
|
||||
Reference in New Issue
Block a user