feat: Add publish and mark-published endpoints with validation

- Add /api/posts/{id}/publish endpoint for API-based publishing
- Add /api/posts/{id}/mark-published endpoint for manual workflow
- Add content length validation before publishing
- Update modal with "Ya lo publiqué" and "Publicar (API)" buttons
- Fix retry_count handling for None values

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 23:01:28 +00:00
parent 855e765417
commit 29520a00f6
4 changed files with 122 additions and 8 deletions

View File

@@ -220,6 +220,62 @@ async def reject_post(post_id: int, db: Session = Depends(get_db)):
return {"message": "Post rechazado", "post_id": post_id}
@router.post("/{post_id}/publish")
async def publish_post_now(post_id: int, db: Session = Depends(get_db)):
"""Publicar un post inmediatamente."""
from worker.tasks.publish_post import publish_to_platform
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 not in ["draft", "pending_approval", "scheduled", "failed"]:
raise HTTPException(
status_code=400,
detail=f"No se puede publicar un post con status '{post.status}'"
)
# Cambiar estado a publishing
post.status = "publishing"
db.commit()
# Encolar tarea de publicación para cada plataforma
for platform in post.platforms:
publish_to_platform.delay(post.id, platform)
return {
"success": True,
"message": "Post enviado a publicación",
"post_id": post_id,
"platforms": post.platforms
}
@router.post("/{post_id}/mark-published")
async def mark_post_as_published(post_id: int, db: Session = Depends(get_db)):
"""Marcar un post como publicado manualmente (sin usar API)."""
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 not in ["draft", "pending_approval", "scheduled", "failed"]:
raise HTTPException(
status_code=400,
detail=f"No se puede marcar un post con status '{post.status}'"
)
post.status = "published"
post.published_at = datetime.utcnow()
post.error_message = None
db.commit()
return {
"success": True,
"message": "Post marcado como publicado",
"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."""

View File

@@ -60,10 +60,32 @@ def publish_post(self, post_id: int):
for platform_name in post.platforms:
try:
platform = Platform(platform_name)
# Get platform-specific content
platform_content = post.get_content_for_platform(platform_name)
# Pre-validate content length with descriptive error
publisher = publisher_manager.get_publisher(platform)
if publisher and hasattr(publisher, 'char_limit'):
content_length = len(platform_content)
if content_length > publisher.char_limit:
error_msg = (
f"Contenido excede límite: {content_length}/{publisher.char_limit} "
f"caracteres (sobra {content_length - publisher.char_limit})"
)
logger.error(f"Validation failed for {platform_name}: {error_msg}")
results[platform_name] = {
"success": False,
"post_id": None,
"url": None,
"error": error_msg
}
continue
result = run_async(
publisher_manager.publish(
platform=platform,
content=post.content,
content=platform_content,
image_path=post.image_url
)
)
@@ -90,10 +112,14 @@ def publish_post(self, post_id: int):
if any_success:
post.status = "published"
post.published_at = datetime.utcnow()
post.publish_results = results
post.platform_post_ids = {
k: v.get("post_id") for k, v in results.items() if v.get("success")
}
else:
post.status = "failed"
post.publish_results = results
# Collect all error messages
errors = [f"{k}: {v.get('error')}" for k, v in results.items() if v.get("error")]
post.error_message = "\n".join(errors) if errors else "Unknown error"
db.commit()