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,150 @@
"""Editorial plans and scheduled posts router.
Manages editorial plans (posting schedules) and individual scheduled post entries.
"""
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 EditorialPlan, ScheduledPost
from ..schemas import (
EditorialPlanCreate,
EditorialPlanResponse,
EditorialPlanUpdate,
ScheduledPostCreate,
ScheduledPostResponse,
)
router = APIRouter(
prefix="/api/plans",
tags=["plans"],
dependencies=[Depends(get_current_user)],
)
# === Editorial Plans ===
@router.get("/", response_model=list[EditorialPlanResponse])
def list_plans(
character_id: int | None = Query(None),
db: Session = Depends(get_db),
):
"""List all editorial plans, optionally filtered by character."""
query = db.query(EditorialPlan)
if character_id is not None:
query = query.filter(EditorialPlan.character_id == character_id)
return query.order_by(EditorialPlan.created_at.desc()).all()
@router.get("/scheduled", response_model=list[ScheduledPostResponse])
def list_all_scheduled_posts(
platform: str | None = Query(None),
status: str | None = Query(None),
date_from: datetime | None = Query(None),
date_after: datetime | None = Query(None),
db: Session = Depends(get_db),
):
"""Get all scheduled posts across all plans with optional filters."""
query = db.query(ScheduledPost)
if platform is not None:
query = query.filter(ScheduledPost.platform == platform)
if status is not None:
query = query.filter(ScheduledPost.status == status)
if date_from is not None:
query = query.filter(ScheduledPost.scheduled_at >= date_from)
if date_after is not None:
query = query.filter(ScheduledPost.scheduled_at <= date_after)
return query.order_by(ScheduledPost.scheduled_at).all()
@router.get("/{plan_id}", response_model=EditorialPlanResponse)
def get_plan(plan_id: int, db: Session = Depends(get_db)):
"""Get a single editorial plan by ID."""
plan = db.query(EditorialPlan).filter(EditorialPlan.id == plan_id).first()
if not plan:
raise HTTPException(status_code=404, detail="Editorial plan not found")
return plan
@router.post("/", response_model=EditorialPlanResponse, status_code=201)
def create_plan(data: EditorialPlanCreate, db: Session = Depends(get_db)):
"""Create a new editorial plan."""
plan = EditorialPlan(**data.model_dump())
db.add(plan)
db.commit()
db.refresh(plan)
return plan
@router.put("/{plan_id}", response_model=EditorialPlanResponse)
def update_plan(
plan_id: int, data: EditorialPlanUpdate, db: Session = Depends(get_db)
):
"""Update an editorial plan."""
plan = db.query(EditorialPlan).filter(EditorialPlan.id == plan_id).first()
if not plan:
raise HTTPException(status_code=404, detail="Editorial plan not found")
update_data = data.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(plan, key, value)
plan.updated_at = datetime.utcnow()
db.commit()
db.refresh(plan)
return plan
@router.delete("/{plan_id}", status_code=204)
def delete_plan(plan_id: int, db: Session = Depends(get_db)):
"""Delete an editorial plan and its associated scheduled posts."""
plan = db.query(EditorialPlan).filter(EditorialPlan.id == plan_id).first()
if not plan:
raise HTTPException(status_code=404, detail="Editorial plan not found")
# Delete associated scheduled posts first
db.query(ScheduledPost).filter(ScheduledPost.plan_id == plan_id).delete()
db.delete(plan)
db.commit()
@router.post("/{plan_id}/toggle", response_model=EditorialPlanResponse)
def toggle_plan(plan_id: int, db: Session = Depends(get_db)):
"""Toggle the is_active status of an editorial plan."""
plan = db.query(EditorialPlan).filter(EditorialPlan.id == plan_id).first()
if not plan:
raise HTTPException(status_code=404, detail="Editorial plan not found")
plan.is_active = not plan.is_active
plan.updated_at = datetime.utcnow()
db.commit()
db.refresh(plan)
return plan
# === Scheduled Posts ===
@router.get("/{plan_id}/schedule", response_model=list[ScheduledPostResponse])
def get_plan_scheduled_posts(plan_id: int, db: Session = Depends(get_db)):
"""Get all scheduled posts for a specific plan."""
plan = db.query(EditorialPlan).filter(EditorialPlan.id == plan_id).first()
if not plan:
raise HTTPException(status_code=404, detail="Editorial plan not found")
return (
db.query(ScheduledPost)
.filter(ScheduledPost.plan_id == plan_id)
.order_by(ScheduledPost.scheduled_at)
.all()
)
@router.post("/schedule", response_model=ScheduledPostResponse, status_code=201)
def schedule_post(data: ScheduledPostCreate, db: Session = Depends(get_db)):
"""Manually schedule a post."""
scheduled = ScheduledPost(**data.model_dump())
db.add(scheduled)
db.commit()
db.refresh(scheduled)
return scheduled