- 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>
113 lines
3.0 KiB
Python
113 lines
3.0 KiB
Python
"""Editorial Calendar router.
|
|
|
|
Espone endpoint per il calendario editoriale con awareness levels e formati narrativi.
|
|
"""
|
|
|
|
import csv
|
|
import io
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from fastapi.responses import StreamingResponse
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..auth import get_current_user
|
|
from ..database import get_db
|
|
from ..services.calendar_service import CalendarService, FORMATI_NARRATIVI, AWARENESS_LEVELS
|
|
|
|
router = APIRouter(
|
|
prefix="/api/editorial",
|
|
tags=["editorial"],
|
|
dependencies=[Depends(get_current_user)],
|
|
)
|
|
|
|
_calendar_service = CalendarService()
|
|
|
|
|
|
# === Schemas locali ===
|
|
|
|
class CalendarGenerateRequest(BaseModel):
|
|
topics: list[str]
|
|
format_narrativo: Optional[str] = None
|
|
awareness_level: Optional[int] = None
|
|
num_posts: int = 7
|
|
start_date: Optional[str] = None
|
|
character_id: Optional[int] = None
|
|
|
|
|
|
class ExportCsvRequest(BaseModel):
|
|
slots: list[dict]
|
|
|
|
|
|
# === Endpoints ===
|
|
|
|
@router.get("/formats")
|
|
def get_formats():
|
|
"""Restituisce i format narrativi disponibili con i relativi awareness levels."""
|
|
return {
|
|
"formats": _calendar_service.get_formats(),
|
|
"awareness_levels": [
|
|
{"value": k, "label": v}
|
|
for k, v in AWARENESS_LEVELS.items()
|
|
],
|
|
}
|
|
|
|
|
|
@router.post("/generate-calendar")
|
|
def generate_calendar(request: CalendarGenerateRequest, db: Session = Depends(get_db)):
|
|
"""Genera un calendario editoriale con awareness levels."""
|
|
if not request.topics:
|
|
return {"slots": [], "totale_post": 0}
|
|
|
|
slots = _calendar_service.generate_calendar(
|
|
topics=request.topics,
|
|
num_posts=request.num_posts,
|
|
format_narrativo=request.format_narrativo,
|
|
awareness_level=request.awareness_level,
|
|
start_date=request.start_date,
|
|
)
|
|
|
|
return {
|
|
"slots": slots,
|
|
"totale_post": len(slots),
|
|
}
|
|
|
|
|
|
@router.post("/export-csv")
|
|
def export_csv(request: ExportCsvRequest):
|
|
"""Esporta il calendario editoriale come CSV per Canva."""
|
|
output = io.StringIO()
|
|
fieldnames = [
|
|
"indice",
|
|
"data_pubblicazione",
|
|
"topic",
|
|
"formato_narrativo",
|
|
"awareness_level",
|
|
"awareness_label",
|
|
"note",
|
|
]
|
|
|
|
writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction="ignore")
|
|
writer.writeheader()
|
|
|
|
for slot in request.slots:
|
|
writer.writerow({
|
|
"indice": slot.get("indice", ""),
|
|
"data_pubblicazione": slot.get("data_pubblicazione", ""),
|
|
"topic": slot.get("topic", ""),
|
|
"formato_narrativo": slot.get("formato_narrativo", ""),
|
|
"awareness_level": slot.get("awareness_level", ""),
|
|
"awareness_label": slot.get("awareness_label", ""),
|
|
"note": slot.get("note", ""),
|
|
})
|
|
|
|
output.seek(0)
|
|
return StreamingResponse(
|
|
iter([output.getvalue()]),
|
|
media_type="text/csv",
|
|
headers={
|
|
"Content-Disposition": "attachment; filename=calendario_editoriale.csv"
|
|
},
|
|
)
|