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:
Michele
2026-03-31 17:23:16 +02:00
commit 519a580679
58 changed files with 8348 additions and 0 deletions

View 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"
},
)