Implementación inicial del sistema de automatización de redes sociales
- 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>
This commit is contained in:
216
app/api/routes/posts.py
Normal file
216
app/api/routes/posts.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
API Routes para gestión de Posts.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
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.post import Post
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ===========================================
|
||||
# SCHEMAS
|
||||
# ===========================================
|
||||
|
||||
class PostCreate(BaseModel):
|
||||
content: str
|
||||
content_type: str
|
||||
platforms: List[str]
|
||||
scheduled_at: Optional[datetime] = None
|
||||
image_url: Optional[str] = None
|
||||
approval_required: bool = False
|
||||
hashtags: Optional[List[str]] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PostUpdate(BaseModel):
|
||||
content: Optional[str] = None
|
||||
content_x: Optional[str] = None
|
||||
content_threads: Optional[str] = None
|
||||
content_instagram: Optional[str] = None
|
||||
content_facebook: Optional[str] = None
|
||||
platforms: Optional[List[str]] = None
|
||||
scheduled_at: Optional[datetime] = None
|
||||
status: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
hashtags: Optional[List[str]] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PostResponse(BaseModel):
|
||||
id: int
|
||||
content: str
|
||||
content_type: str
|
||||
platforms: List[str]
|
||||
status: str
|
||||
scheduled_at: Optional[datetime]
|
||||
published_at: Optional[datetime]
|
||||
image_url: Optional[str]
|
||||
approval_required: bool
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ===========================================
|
||||
# ENDPOINTS
|
||||
# ===========================================
|
||||
|
||||
@router.get("/", response_model=List[PostResponse])
|
||||
async def list_posts(
|
||||
status: Optional[str] = Query(None, description="Filtrar por estado"),
|
||||
content_type: Optional[str] = Query(None, description="Filtrar por tipo"),
|
||||
platform: Optional[str] = Query(None, description="Filtrar por plataforma"),
|
||||
limit: int = Query(50, le=100),
|
||||
offset: int = Query(0),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Listar posts con filtros opcionales."""
|
||||
query = db.query(Post)
|
||||
|
||||
if status:
|
||||
query = query.filter(Post.status == status)
|
||||
if content_type:
|
||||
query = query.filter(Post.content_type == content_type)
|
||||
if platform:
|
||||
query = query.filter(Post.platforms.contains([platform]))
|
||||
|
||||
posts = query.order_by(Post.created_at.desc()).offset(offset).limit(limit).all()
|
||||
return posts
|
||||
|
||||
|
||||
@router.get("/pending")
|
||||
async def list_pending_posts(db: Session = Depends(get_db)):
|
||||
"""Listar posts pendientes de aprobación."""
|
||||
posts = db.query(Post).filter(
|
||||
Post.status == "pending_approval"
|
||||
).order_by(Post.scheduled_at.asc()).all()
|
||||
|
||||
return [post.to_dict() for post in posts]
|
||||
|
||||
|
||||
@router.get("/scheduled")
|
||||
async def list_scheduled_posts(db: Session = Depends(get_db)):
|
||||
"""Listar posts programados."""
|
||||
posts = db.query(Post).filter(
|
||||
Post.status == "scheduled",
|
||||
Post.scheduled_at >= datetime.utcnow()
|
||||
).order_by(Post.scheduled_at.asc()).all()
|
||||
|
||||
return [post.to_dict() for post in posts]
|
||||
|
||||
|
||||
@router.get("/{post_id}")
|
||||
async def get_post(post_id: int, db: Session = Depends(get_db)):
|
||||
"""Obtener un post por ID."""
|
||||
post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post no encontrado")
|
||||
return post.to_dict()
|
||||
|
||||
|
||||
@router.post("/", response_model=PostResponse)
|
||||
async def create_post(post_data: PostCreate, db: Session = Depends(get_db)):
|
||||
"""Crear un nuevo post."""
|
||||
post = Post(
|
||||
content=post_data.content,
|
||||
content_type=post_data.content_type,
|
||||
platforms=post_data.platforms,
|
||||
scheduled_at=post_data.scheduled_at,
|
||||
image_url=post_data.image_url,
|
||||
approval_required=post_data.approval_required,
|
||||
hashtags=post_data.hashtags,
|
||||
status="pending_approval" if post_data.approval_required else "scheduled"
|
||||
)
|
||||
|
||||
db.add(post)
|
||||
db.commit()
|
||||
db.refresh(post)
|
||||
|
||||
return post
|
||||
|
||||
|
||||
@router.put("/{post_id}")
|
||||
async def update_post(post_id: int, post_data: PostUpdate, db: Session = Depends(get_db)):
|
||||
"""Actualizar un post."""
|
||||
post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post no encontrado")
|
||||
|
||||
update_data = post_data.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(post, field, value)
|
||||
|
||||
post.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
db.refresh(post)
|
||||
|
||||
return post.to_dict()
|
||||
|
||||
|
||||
@router.post("/{post_id}/approve")
|
||||
async def approve_post(post_id: int, db: Session = Depends(get_db)):
|
||||
"""Aprobar un post pendiente."""
|
||||
post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post no encontrado")
|
||||
|
||||
if post.status != "pending_approval":
|
||||
raise HTTPException(status_code=400, detail="El post no está pendiente de aprobación")
|
||||
|
||||
post.status = "scheduled"
|
||||
post.approved_at = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
return {"message": "Post aprobado", "post_id": post_id}
|
||||
|
||||
|
||||
@router.post("/{post_id}/reject")
|
||||
async def reject_post(post_id: int, db: Session = Depends(get_db)):
|
||||
"""Rechazar un post pendiente."""
|
||||
post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post no encontrado")
|
||||
|
||||
post.status = "cancelled"
|
||||
db.commit()
|
||||
|
||||
return {"message": "Post rechazado", "post_id": post_id}
|
||||
|
||||
|
||||
@router.post("/{post_id}/regenerate")
|
||||
async def regenerate_post(post_id: int, db: Session = Depends(get_db)):
|
||||
"""Regenerar contenido de un post con IA."""
|
||||
post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post no encontrado")
|
||||
|
||||
# TODO: Implementar regeneración con DeepSeek
|
||||
# from app.services.content_generator import regenerate_content
|
||||
# new_content = await regenerate_content(post)
|
||||
|
||||
return {"message": "Regeneración en desarrollo", "post_id": post_id}
|
||||
|
||||
|
||||
@router.delete("/{post_id}")
|
||||
async def delete_post(post_id: int, db: Session = Depends(get_db)):
|
||||
"""Eliminar un post."""
|
||||
post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post no encontrado")
|
||||
|
||||
db.delete(post)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Post eliminado", "post_id": post_id}
|
||||
Reference in New Issue
Block a user