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:
112
backend/app/routers/editorial.py
Normal file
112
backend/app/routers/editorial.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""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"
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user