- Estructura completa del proyecto con FastAPI - Modelos de base de datos (productos, servicios, posts, calendario, interacciones) - Publishers para X, Threads, Instagram, Facebook - Generador de contenido con DeepSeek API - Worker de Celery con tareas programadas - Dashboard básico con templates HTML - Docker Compose para despliegue - Documentación completa Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
202 lines
6.1 KiB
Python
202 lines
6.1 KiB
Python
"""
|
|
API Routes para gestión del Calendario de Contenido.
|
|
"""
|
|
|
|
from datetime import datetime, time
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from pydantic import BaseModel
|
|
|
|
from app.core.database import get_db
|
|
from app.models.content_calendar import ContentCalendar
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ===========================================
|
|
# SCHEMAS
|
|
# ===========================================
|
|
|
|
class CalendarEntryCreate(BaseModel):
|
|
day_of_week: int # 0=Lunes, 6=Domingo
|
|
time: str # Formato "HH:MM"
|
|
content_type: str
|
|
platforms: List[str]
|
|
requires_approval: bool = False
|
|
category_filter: Optional[str] = None
|
|
priority: int = 0
|
|
description: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class CalendarEntryUpdate(BaseModel):
|
|
day_of_week: Optional[int] = None
|
|
time: Optional[str] = None
|
|
content_type: Optional[str] = None
|
|
platforms: Optional[List[str]] = None
|
|
is_active: Optional[bool] = None
|
|
requires_approval: Optional[bool] = None
|
|
category_filter: Optional[str] = None
|
|
priority: Optional[int] = None
|
|
description: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ===========================================
|
|
# ENDPOINTS
|
|
# ===========================================
|
|
|
|
@router.get("/")
|
|
async def list_calendar_entries(
|
|
day_of_week: Optional[int] = Query(None),
|
|
content_type: Optional[str] = Query(None),
|
|
platform: Optional[str] = Query(None),
|
|
is_active: Optional[bool] = Query(True),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Listar entradas del calendario."""
|
|
query = db.query(ContentCalendar)
|
|
|
|
if day_of_week is not None:
|
|
query = query.filter(ContentCalendar.day_of_week == day_of_week)
|
|
if content_type:
|
|
query = query.filter(ContentCalendar.content_type == content_type)
|
|
if is_active is not None:
|
|
query = query.filter(ContentCalendar.is_active == is_active)
|
|
if platform:
|
|
query = query.filter(ContentCalendar.platforms.contains([platform]))
|
|
|
|
entries = query.order_by(
|
|
ContentCalendar.day_of_week,
|
|
ContentCalendar.time
|
|
).all()
|
|
|
|
return [e.to_dict() for e in entries]
|
|
|
|
|
|
@router.get("/week")
|
|
async def get_week_schedule(db: Session = Depends(get_db)):
|
|
"""Obtener el calendario semanal completo."""
|
|
entries = db.query(ContentCalendar).filter(
|
|
ContentCalendar.is_active == True
|
|
).order_by(
|
|
ContentCalendar.day_of_week,
|
|
ContentCalendar.time
|
|
).all()
|
|
|
|
# Organizar por día
|
|
week = {i: [] for i in range(7)}
|
|
for entry in entries:
|
|
week[entry.day_of_week].append(entry.to_dict())
|
|
|
|
days = ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"]
|
|
return {
|
|
days[i]: week[i] for i in range(7)
|
|
}
|
|
|
|
|
|
@router.get("/today")
|
|
async def get_today_schedule(db: Session = Depends(get_db)):
|
|
"""Obtener el calendario de hoy."""
|
|
today = datetime.now().weekday()
|
|
|
|
entries = db.query(ContentCalendar).filter(
|
|
ContentCalendar.day_of_week == today,
|
|
ContentCalendar.is_active == True
|
|
).order_by(ContentCalendar.time).all()
|
|
|
|
return [e.to_dict() for e in entries]
|
|
|
|
|
|
@router.get("/{entry_id}")
|
|
async def get_calendar_entry(entry_id: int, db: Session = Depends(get_db)):
|
|
"""Obtener una entrada del calendario por ID."""
|
|
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
|
|
if not entry:
|
|
raise HTTPException(status_code=404, detail="Entrada no encontrada")
|
|
return entry.to_dict()
|
|
|
|
|
|
@router.post("/")
|
|
async def create_calendar_entry(entry_data: CalendarEntryCreate, db: Session = Depends(get_db)):
|
|
"""Crear una nueva entrada en el calendario."""
|
|
# Parsear hora
|
|
hour, minute = map(int, entry_data.time.split(":"))
|
|
entry_time = time(hour=hour, minute=minute)
|
|
|
|
entry = ContentCalendar(
|
|
day_of_week=entry_data.day_of_week,
|
|
time=entry_time,
|
|
content_type=entry_data.content_type,
|
|
platforms=entry_data.platforms,
|
|
requires_approval=entry_data.requires_approval,
|
|
category_filter=entry_data.category_filter,
|
|
priority=entry_data.priority,
|
|
description=entry_data.description
|
|
)
|
|
|
|
db.add(entry)
|
|
db.commit()
|
|
db.refresh(entry)
|
|
return entry.to_dict()
|
|
|
|
|
|
@router.put("/{entry_id}")
|
|
async def update_calendar_entry(
|
|
entry_id: int,
|
|
entry_data: CalendarEntryUpdate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Actualizar una entrada del calendario."""
|
|
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
|
|
if not entry:
|
|
raise HTTPException(status_code=404, detail="Entrada no encontrada")
|
|
|
|
update_data = entry_data.dict(exclude_unset=True)
|
|
|
|
# Parsear hora si se proporciona
|
|
if "time" in update_data and update_data["time"]:
|
|
hour, minute = map(int, update_data["time"].split(":"))
|
|
update_data["time"] = time(hour=hour, minute=minute)
|
|
|
|
for field, value in update_data.items():
|
|
setattr(entry, field, value)
|
|
|
|
entry.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(entry)
|
|
return entry.to_dict()
|
|
|
|
|
|
@router.delete("/{entry_id}")
|
|
async def delete_calendar_entry(entry_id: int, db: Session = Depends(get_db)):
|
|
"""Eliminar una entrada del calendario."""
|
|
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
|
|
if not entry:
|
|
raise HTTPException(status_code=404, detail="Entrada no encontrada")
|
|
|
|
db.delete(entry)
|
|
db.commit()
|
|
return {"message": "Entrada eliminada", "entry_id": entry_id}
|
|
|
|
|
|
@router.post("/{entry_id}/toggle")
|
|
async def toggle_calendar_entry(entry_id: int, db: Session = Depends(get_db)):
|
|
"""Activar/desactivar una entrada del calendario."""
|
|
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
|
|
if not entry:
|
|
raise HTTPException(status_code=404, detail="Entrada no encontrada")
|
|
|
|
entry.is_active = not entry.is_active
|
|
db.commit()
|
|
|
|
return {
|
|
"message": f"Entrada {'activada' if entry.is_active else 'desactivada'}",
|
|
"is_active": entry.is_active
|
|
}
|